From be957d53a1c54dc0097bf6710fafbed9e2a84fc9 Mon Sep 17 00:00:00 2001 From: ledwards2225 <98505400+ledwards2225@users.noreply.github.com> Date: Wed, 6 Dec 2023 10:20:14 -0700 Subject: [PATCH] feat: Encapsulated Goblin (#3524) Adds a class to encapsulate Goblin. Uses this to thread a single transcript through the two VMs for proper challenge generation. The Goblin class has an interface via function: `accumulate` (construct a proof and a merge proof), `prove` (run the VM proofs) and `verify` (a testing function to natively verify those proofs. Rewrites the Goblin tests accordingly. Adds tests that use recursive verification of Honk proofs. When we have recursive merge verification we will have something worth measuring the assess the performance of Goblin proving. Closes https://github.com/AztecProtocol/barretenberg/issues/785 Closes https://github.com/AztecProtocol/barretenberg/issues/786 --------- Co-authored-by: codygunton --- .../honk_bench/benchmark_utilities.hpp | 2 +- cpp/src/barretenberg/eccvm/eccvm_prover.cpp | 11 +- cpp/src/barretenberg/eccvm/eccvm_prover.hpp | 1 + cpp/src/barretenberg/eccvm/eccvm_verifier.cpp | 9 +- .../flavor/goblin_ultra_recursive.hpp | 2 +- .../barretenberg/flavor/ultra_recursive.hpp | 2 +- cpp/src/barretenberg/goblin/CMakeLists.txt | 2 +- .../goblin/full_goblin_composer.test.cpp | 163 ++---------------- cpp/src/barretenberg/goblin/goblin.hpp | 106 ++++++++++++ cpp/src/barretenberg/goblin/mock_circuits.hpp | 101 +++++++++++ .../goblin_translator_circuit_builder.hpp | 1 + .../protogalaxy/protogalaxy_prover.hpp | 4 +- .../stdlib/recursion/CMakeLists.txt | 2 +- .../goblin/full_goblin_recursion.test.cpp | 96 +++++++++++ .../verifier/ultra_recursive_verifier.cpp | 4 +- .../verifier/ultra_recursive_verifier.hpp | 3 +- .../barretenberg/sumcheck/sumcheck_round.hpp | 4 +- .../barretenberg/transcript/transcript.hpp | 27 ++- .../goblin_translator_composer.test.cpp | 42 +++-- .../goblin_translator_prover.cpp | 11 +- .../goblin_translator_prover.hpp | 1 + .../goblin_translator_verifier.cpp | 6 +- .../barretenberg/ultra_honk/merge_prover.cpp | 2 +- .../ultra_honk/merge_verifier.cpp | 2 +- 24 files changed, 406 insertions(+), 198 deletions(-) create mode 100644 cpp/src/barretenberg/goblin/goblin.hpp create mode 100644 cpp/src/barretenberg/goblin/mock_circuits.hpp create mode 100644 cpp/src/barretenberg/stdlib/recursion/goblin/full_goblin_recursion.test.cpp diff --git a/cpp/src/barretenberg/benchmark/honk_bench/benchmark_utilities.hpp b/cpp/src/barretenberg/benchmark/honk_bench/benchmark_utilities.hpp index 6f223939a4..0ef9fabfa0 100644 --- a/cpp/src/barretenberg/benchmark/honk_bench/benchmark_utilities.hpp +++ b/cpp/src/barretenberg/benchmark/honk_bench/benchmark_utilities.hpp @@ -218,7 +218,7 @@ void construct_proof_with_specified_num_iterations( Composer composer; for (auto _ : state) { - // Constuct circuit and prover; don't include this part in measurement + // Construct circuit and prover; don't include this part in measurement state.PauseTiming(); auto prover = get_prover(composer, test_circuit_function, num_iterations); state.ResumeTiming(); diff --git a/cpp/src/barretenberg/eccvm/eccvm_prover.cpp b/cpp/src/barretenberg/eccvm/eccvm_prover.cpp index 16d3f3a9cf..f9b9fb6ea4 100644 --- a/cpp/src/barretenberg/eccvm/eccvm_prover.cpp +++ b/cpp/src/barretenberg/eccvm/eccvm_prover.cpp @@ -345,31 +345,34 @@ template void ECCVMProver_::execute_transcript_cons transcript->send_to_verifier("Translation:hack_evaluation", hack.evaluate(evaluation_challenge_x)); // Get another challenge for batching the univariate claims - FF batching_challenge = transcript->get_challenge("Translation:batching_challenge"); + FF ipa_batching_challenge = transcript->get_challenge("Translation:ipa_batching_challenge"); // Collect the polynomials and evaluations to be batched RefArray univariate_polynomials{ key->transcript_op, key->transcript_Px, key->transcript_Py, key->transcript_z1, key->transcript_z2, hack }; std::array univariate_evaluations; - // Constuct the batched polynomial and batched evaluation + // Construct the batched polynomial and batched evaluation Polynomial batched_univariate{ key->circuit_size }; FF batched_evaluation{ 0 }; auto batching_scalar = FF(1); for (auto [polynomial, eval] : zip_view(univariate_polynomials, univariate_evaluations)) { batched_univariate.add_scaled(polynomial, batching_scalar); batched_evaluation += eval * batching_scalar; - batching_scalar *= batching_challenge; + batching_scalar *= ipa_batching_challenge; } // Compute a proof for the batched univariate opening PCS::compute_opening_proof( commitment_key, { evaluation_challenge_x, batched_evaluation }, batched_univariate, transcript); + + // Get another challenge for batching the univariate claims + translation_batching_challenge_v = transcript->get_challenge("Translation:batching_challenge"); } template plonk::proof& ECCVMProver_::export_proof() { - proof.proof_data = transcript->proof_data; + proof.proof_data = transcript->export_proof(); return proof; } diff --git a/cpp/src/barretenberg/eccvm/eccvm_prover.hpp b/cpp/src/barretenberg/eccvm/eccvm_prover.hpp index 39b42b8968..af6f4ec457 100644 --- a/cpp/src/barretenberg/eccvm/eccvm_prover.hpp +++ b/cpp/src/barretenberg/eccvm/eccvm_prover.hpp @@ -69,6 +69,7 @@ template class ECCVMProver_ { Polynomial quotient_W; FF evaluation_challenge_x; + FF translation_batching_challenge_v; // to be rederived by the translator verifier sumcheck::SumcheckOutput sumcheck_output; pcs::gemini::ProverOutput gemini_output; diff --git a/cpp/src/barretenberg/eccvm/eccvm_verifier.cpp b/cpp/src/barretenberg/eccvm/eccvm_verifier.cpp index d7f26ce729..76058a9c80 100644 --- a/cpp/src/barretenberg/eccvm/eccvm_verifier.cpp +++ b/cpp/src/barretenberg/eccvm/eccvm_verifier.cpp @@ -253,16 +253,17 @@ template bool ECCVMVerifier_::verify_proof(const plonk transcript->template receive_from_prover("Translation:hack_evaluation") }; - FF batching_challenge = transcript->get_challenge("Translation:batching_challenge"); + // Get another challenge for batching the univariate claims + FF ipa_batching_challenge = transcript->get_challenge("Translation:ipa_batching_challenge"); - // Constuct batched commitment and batched evaluation + // Construct batched commitment and batched evaluation auto batched_commitment = transcript_commitments[0]; auto batched_transcript_eval = transcript_evaluations[0]; - auto batching_scalar = batching_challenge; + auto batching_scalar = ipa_batching_challenge; for (size_t idx = 1; idx < transcript_commitments.size(); ++idx) { batched_commitment = batched_commitment + transcript_commitments[idx] * batching_scalar; batched_transcript_eval += batching_scalar * transcript_evaluations[idx]; - batching_scalar *= batching_challenge; + batching_scalar *= ipa_batching_challenge; } // Construct and verify batched opening claim diff --git a/cpp/src/barretenberg/flavor/goblin_ultra_recursive.hpp b/cpp/src/barretenberg/flavor/goblin_ultra_recursive.hpp index 79b573b02e..fb6e075136 100644 --- a/cpp/src/barretenberg/flavor/goblin_ultra_recursive.hpp +++ b/cpp/src/barretenberg/flavor/goblin_ultra_recursive.hpp @@ -107,7 +107,7 @@ template class GoblinUltraRecursive_ { * @param builder * @param native_key Native verification key from which to extract the precomputed commitments */ - VerificationKey(CircuitBuilder* builder, std::shared_ptr native_key) + VerificationKey(CircuitBuilder* builder, const std::shared_ptr& native_key) : VerificationKey_>(native_key->circuit_size, native_key->num_public_inputs) { diff --git a/cpp/src/barretenberg/flavor/ultra_recursive.hpp b/cpp/src/barretenberg/flavor/ultra_recursive.hpp index 5f068a5c2d..df0ac96c12 100644 --- a/cpp/src/barretenberg/flavor/ultra_recursive.hpp +++ b/cpp/src/barretenberg/flavor/ultra_recursive.hpp @@ -251,7 +251,7 @@ template class UltraRecursive_ { * @param builder * @param native_key Native verification key from which to extract the precomputed commitments */ - VerificationKey(CircuitBuilder* builder, std::shared_ptr native_key) + VerificationKey(CircuitBuilder* builder, const std::shared_ptr& native_key) : VerificationKey_>(native_key->circuit_size, native_key->num_public_inputs) { this->q_m = Commitment::from_witness(builder, native_key->q_m); diff --git a/cpp/src/barretenberg/goblin/CMakeLists.txt b/cpp/src/barretenberg/goblin/CMakeLists.txt index 248b05c02e..a6c3c61383 100644 --- a/cpp/src/barretenberg/goblin/CMakeLists.txt +++ b/cpp/src/barretenberg/goblin/CMakeLists.txt @@ -1 +1 @@ -barretenberg_module(goblin ultra_honk eccvm translator_vm) \ No newline at end of file +barretenberg_module(goblin ultra_honk eccvm translator_vm transcript) \ No newline at end of file diff --git a/cpp/src/barretenberg/goblin/full_goblin_composer.test.cpp b/cpp/src/barretenberg/goblin/full_goblin_composer.test.cpp index 0956398701..7503307881 100644 --- a/cpp/src/barretenberg/goblin/full_goblin_composer.test.cpp +++ b/cpp/src/barretenberg/goblin/full_goblin_composer.test.cpp @@ -1,4 +1,6 @@ #include "barretenberg/eccvm/eccvm_composer.hpp" +#include "barretenberg/goblin/goblin.hpp" +#include "barretenberg/goblin/mock_circuits.hpp" #include "barretenberg/goblin/translation_evaluations.hpp" #include "barretenberg/proof_system/circuit_builder/eccvm/eccvm_circuit_builder.hpp" #include "barretenberg/proof_system/circuit_builder/goblin_ultra_circuit_builder.hpp" @@ -8,14 +10,11 @@ #include +using namespace barretenberg; using namespace proof_system::honk; namespace test_full_goblin_composer { -namespace { -auto& engine = numeric::random::get_debug_engine(); -} - class FullGoblinComposerTests : public ::testing::Test { protected: static void SetUpTestSuite() @@ -30,110 +29,11 @@ class FullGoblinComposerTests : public ::testing::Test { using Point = Curve::AffineElement; using CommitmentKey = pcs::CommitmentKey; using OpQueue = proof_system::ECCOpQueue; + using GoblinUltraBuilder = proof_system::GoblinUltraCircuitBuilder; using ECCVMFlavor = flavor::ECCVM; using ECCVMBuilder = proof_system::ECCVMCircuitBuilder; using ECCVMComposer = ECCVMComposer_; - - static constexpr size_t NUM_OP_QUEUE_COLUMNS = flavor::GoblinUltra::NUM_WIRES; - - /** - * @brief Generate a simple test circuit with some ECC op gates and conventional arithmetic gates - * - * @param builder - */ - static void generate_test_circuit(proof_system::GoblinUltraCircuitBuilder& builder) - { - // Add some arbitrary ecc op gates - for (size_t i = 0; i < 3; ++i) { - auto point = Point::random_element(); - auto scalar = FF::random_element(); - builder.queue_ecc_add_accum(point); - builder.queue_ecc_mul_accum(point, scalar); - } - // queues the result of the preceding ECC - builder.queue_ecc_eq(); // should be eq and reset - - // Add some conventional gates that utilize public inputs - for (size_t i = 0; i < 10; ++i) { - FF a = FF::random_element(); - FF b = FF::random_element(); - FF c = FF::random_element(); - FF d = a + b + c; - uint32_t a_idx = builder.add_public_variable(a); - uint32_t b_idx = builder.add_variable(b); - uint32_t c_idx = builder.add_variable(c); - uint32_t d_idx = builder.add_variable(d); - - builder.create_big_add_gate({ a_idx, b_idx, c_idx, d_idx, FF(1), FF(1), FF(1), FF(-1), FF(0) }); - } - } - - /** - * @brief Mock the interactions of a simple curcuit with the op_queue - * @details The transcript aggregation protocol in the Goblin proof system can not yet support an empty "previous - * transcript" (see issue #723). This function mocks the interactions with the op queue of a fictional "first" - * circuit. This way, when we go to generate a proof over our first "real" circuit, the transcript aggregation - * protocol can proceed nominally. The mock data is valid in the sense that it can be processed by all stages of - * Goblin as if it came from a genuine circuit. - * - * @todo WOKTODO: this is a zero commitments issue - * - * @param op_queue - */ - static void perform_op_queue_interactions_for_mock_first_circuit( - std::shared_ptr& op_queue) - { - proof_system::GoblinUltraCircuitBuilder builder{ op_queue }; - - // Add a mul accum op and an equality op - auto point = Point::one() * FF::random_element(); - auto scalar = FF::random_element(); - builder.queue_ecc_mul_accum(point, scalar); - builder.queue_ecc_eq(); - - op_queue->set_size_data(); - - // Manually compute the op queue transcript commitments (which would normally be done by the prover) - auto crs_factory_ = barretenberg::srs::get_crs_factory(); - auto commitment_key = CommitmentKey(op_queue->get_current_size(), crs_factory_); - std::array op_queue_commitments; - size_t idx = 0; - for (auto& entry : op_queue->get_aggregate_transcript()) { - op_queue_commitments[idx++] = commitment_key.commit(entry); - } - // Store the commitment data for use by the prover of the next circuit - op_queue->set_commitment_data(op_queue_commitments); - } - - /** - * @brief Construct and a verify a Honk proof - * - */ - static bool construct_and_verify_honk_proof(GoblinUltraComposer& composer, - proof_system::GoblinUltraCircuitBuilder& builder) - { - auto instance = composer.create_instance(builder); - auto prover = composer.create_prover(instance); - auto verifier = composer.create_verifier(instance); - auto proof = prover.construct_proof(); - bool verified = verifier.verify_proof(proof); - - return verified; - } - - /** - * @brief Construct and verify a Goblin ECC op queue merge proof - * - */ - static bool construct_and_verify_merge_proof(GoblinUltraComposer& composer, std::shared_ptr& op_queue) - { - auto merge_prover = composer.create_merge_prover(op_queue); - auto merge_verifier = composer.create_merge_verifier(/*srs_size=*/10); - auto merge_proof = merge_prover.construct_proof(); - bool verified = merge_verifier.verify_proof(merge_proof); - - return verified; - } + using KernelInput = Goblin::AccumulationOutput; }; /** @@ -145,52 +45,23 @@ class FullGoblinComposerTests : public ::testing::Test { */ TEST_F(FullGoblinComposerTests, SimpleCircuit) { - auto op_queue = std::make_shared(); - - // Add mock data to op queue to simulate interaction with a "first" circuit - perform_op_queue_interactions_for_mock_first_circuit(op_queue); + barretenberg::Goblin goblin; + GoblinUltraBuilder initial_circuit{ goblin.op_queue }; + GoblinTestingUtils::construct_simple_initial_circuit(initial_circuit); + KernelInput kernel_input = goblin.accumulate(initial_circuit); // Construct a series of simple Goblin circuits; generate and verify their proofs - size_t NUM_CIRCUITS = 3; + size_t NUM_CIRCUITS = 2; for (size_t circuit_idx = 0; circuit_idx < NUM_CIRCUITS; ++circuit_idx) { - proof_system::GoblinUltraCircuitBuilder builder{ op_queue }; - - generate_test_circuit(builder); - - // The same composer is used to manage Honk and Merge prover/verifier - proof_system::honk::GoblinUltraComposer composer; - - // Construct and verify Ultra Goblin Honk proof - bool honk_verified = construct_and_verify_honk_proof(composer, builder); - EXPECT_TRUE(honk_verified); - - // Construct and verify op queue merge proof - bool merge_verified = construct_and_verify_merge_proof(composer, op_queue); - EXPECT_TRUE(merge_verified); + GoblinUltraBuilder circuit_builder{ goblin.op_queue }; + GoblinTestingUtils::construct_arithmetic_circuit(circuit_builder); + kernel_input = goblin.accumulate(circuit_builder); } - // Execute the ECCVM - // TODO(https://github.com/AztecProtocol/barretenberg/issues/785) Properly initialize transcript - auto eccvm_builder = ECCVMBuilder(op_queue); - auto eccvm_composer = ECCVMComposer(); - auto eccvm_prover = eccvm_composer.create_prover(eccvm_builder); - auto eccvm_verifier = eccvm_composer.create_verifier(eccvm_builder); - auto eccvm_proof = eccvm_prover.construct_proof(); - bool eccvm_verified = eccvm_verifier.verify_proof(eccvm_proof); - EXPECT_TRUE(eccvm_verified); - - // Execute the Translator - // TODO(https://github.com/AztecProtocol/barretenberg/issues/786) Properly derive batching_challenge - auto batching_challenge = Fbase::random_element(); - auto evaluation_input = eccvm_prover.evaluation_challenge_x; - proof_system::GoblinTranslatorCircuitBuilder translator_builder{ batching_challenge, evaluation_input, op_queue }; - GoblinTranslatorComposer translator_composer; - GoblinTranslatorProver translator_prover = translator_composer.create_prover(translator_builder); - GoblinTranslatorVerifier translator_verifier = translator_composer.create_verifier(translator_builder); - proof_system::plonk::proof translator_proof = translator_prover.construct_proof(); - bool accumulator_construction_verified = translator_verifier.verify_proof(translator_proof); - bool translation_verified = translator_verifier.verify_translation(eccvm_prover.translation_evaluations); - EXPECT_TRUE(accumulator_construction_verified && translation_verified); + Goblin::Proof proof = goblin.prove(); + bool verified = goblin.verify(proof); + EXPECT_TRUE(verified); } + // TODO(https://github.com/AztecProtocol/barretenberg/issues/787) Expand these tests. } // namespace test_full_goblin_composer diff --git a/cpp/src/barretenberg/goblin/goblin.hpp b/cpp/src/barretenberg/goblin/goblin.hpp new file mode 100644 index 0000000000..315b66657f --- /dev/null +++ b/cpp/src/barretenberg/goblin/goblin.hpp @@ -0,0 +1,106 @@ +#pragma once + +#include "barretenberg/eccvm/eccvm_composer.hpp" +#include "barretenberg/proof_system/circuit_builder/eccvm/eccvm_circuit_builder.hpp" +#include "barretenberg/proof_system/circuit_builder/goblin_translator_circuit_builder.hpp" +#include "barretenberg/proof_system/circuit_builder/goblin_ultra_circuit_builder.hpp" +#include "barretenberg/translator_vm/goblin_translator_composer.hpp" +#include "barretenberg/ultra_honk/ultra_composer.hpp" + +namespace barretenberg { + +class Goblin { + using HonkProof = proof_system::plonk::proof; + + public: + /** + * @brief Output of goblin::accumulate; an Ultra proof and the corresponding verification key + * + */ + struct AccumulationOutput { + using NativeVerificationKey = proof_system::honk::flavor::GoblinUltra::VerificationKey; + HonkProof proof; + std::shared_ptr verification_key; + }; + + struct Proof { + HonkProof eccvm_proof; + HonkProof translator_proof; + TranslationEvaluations translation_evaluations; + }; + + using Fr = barretenberg::fr; + using Fq = barretenberg::fq; + + using Transcript = proof_system::honk::BaseTranscript; + using GoblinUltraComposer = proof_system::honk::GoblinUltraComposer; + using GoblinUltraCircuitBuilder = proof_system::GoblinUltraCircuitBuilder; + using OpQueue = proof_system::ECCOpQueue; + using ECCVMFlavor = proof_system::honk::flavor::ECCVM; + using ECCVMBuilder = proof_system::ECCVMCircuitBuilder; + using ECCVMComposer = proof_system::honk::ECCVMComposer; + using TranslatorBuilder = proof_system::GoblinTranslatorCircuitBuilder; + using TranslatorComposer = proof_system::honk::GoblinTranslatorComposer; + + std::shared_ptr op_queue = std::make_shared(); + + private: + // TODO(https://github.com/AztecProtocol/barretenberg/issues/798) unique_ptr use is a hack + std::unique_ptr eccvm_builder; + std::unique_ptr translator_builder; + std::unique_ptr eccvm_composer; + std::unique_ptr translator_composer; + + public: + /** + * @brief + * + * @param circuit_builder + */ + AccumulationOutput accumulate(GoblinUltraCircuitBuilder& circuit_builder) + { + // TODO(https://github.com/AztecProtocol/barretenberg/issues/797) Complete the "kernel" logic by recursively + // verifying previous merge proof + + GoblinUltraComposer composer; + auto instance = composer.create_instance(circuit_builder); + auto prover = composer.create_prover(instance); + auto ultra_proof = prover.construct_proof(); + + auto merge_prover = composer.create_merge_prover(op_queue); + [[maybe_unused]] auto merge_proof = merge_prover.construct_proof(); + + return { ultra_proof, instance->verification_key }; + }; + + Proof prove() + { + Proof proof; + eccvm_builder = std::make_unique(op_queue); + eccvm_composer = std::make_unique(); + auto eccvm_prover = eccvm_composer->create_prover(*eccvm_builder); + proof.eccvm_proof = eccvm_prover.construct_proof(); + proof.translation_evaluations = eccvm_prover.translation_evaluations; + + translator_builder = std::make_unique( + eccvm_prover.translation_batching_challenge_v, eccvm_prover.evaluation_challenge_x, op_queue); + translator_composer = std::make_unique(); + auto translator_prover = translator_composer->create_prover(*translator_builder, eccvm_prover.transcript); + proof.translator_proof = translator_prover.construct_proof(); + return proof; + }; + + bool verify(const Proof& proof) + { + auto eccvm_verifier = eccvm_composer->create_verifier(*eccvm_builder); + bool eccvm_verified = eccvm_verifier.verify_proof(proof.eccvm_proof); + + auto translator_verifier = translator_composer->create_verifier(*translator_builder, eccvm_verifier.transcript); + bool accumulator_construction_verified = translator_verifier.verify_proof(proof.translator_proof); + // TODO(https://github.com/AztecProtocol/barretenberg/issues/799): + // Ensure translation_evaluations are passed correctly + bool translation_verified = translator_verifier.verify_translation(proof.translation_evaluations); + return eccvm_verified && accumulator_construction_verified && translation_verified; + }; +}; +} // namespace barretenberg \ No newline at end of file diff --git a/cpp/src/barretenberg/goblin/mock_circuits.hpp b/cpp/src/barretenberg/goblin/mock_circuits.hpp new file mode 100644 index 0000000000..cb12bb08c3 --- /dev/null +++ b/cpp/src/barretenberg/goblin/mock_circuits.hpp @@ -0,0 +1,101 @@ +#include "barretenberg/commitment_schemes/commitment_key.hpp" +#include "barretenberg/eccvm/eccvm_composer.hpp" +#include "barretenberg/goblin/goblin.hpp" +#include "barretenberg/goblin/translation_evaluations.hpp" +#include "barretenberg/proof_system/circuit_builder/eccvm/eccvm_circuit_builder.hpp" +#include "barretenberg/proof_system/circuit_builder/goblin_ultra_circuit_builder.hpp" +#include "barretenberg/proof_system/circuit_builder/ultra_circuit_builder.hpp" +#include "barretenberg/translator_vm/goblin_translator_composer.hpp" +#include "barretenberg/ultra_honk/ultra_composer.hpp" + +namespace barretenberg { +class GoblinTestingUtils { + public: + using Curve = curve::BN254; + using FF = Curve::ScalarField; + using Fbase = Curve::BaseField; + using Point = Curve::AffineElement; + using CommitmentKey = proof_system::honk::pcs::CommitmentKey; + using OpQueue = proof_system::ECCOpQueue; + using GoblinUltraBuilder = proof_system::GoblinUltraCircuitBuilder; + using Flavor = proof_system::honk::flavor::GoblinUltra; + static constexpr size_t NUM_OP_QUEUE_COLUMNS = Flavor::NUM_WIRES; + + static void construct_arithmetic_circuit(GoblinUltraBuilder& builder) + { + // Add some arithmetic gates that utilize public inputs + for (size_t i = 0; i < 10; ++i) { + FF a = FF::random_element(); + FF b = FF::random_element(); + FF c = FF::random_element(); + FF d = a + b + c; + uint32_t a_idx = builder.add_public_variable(a); + uint32_t b_idx = builder.add_variable(b); + uint32_t c_idx = builder.add_variable(c); + uint32_t d_idx = builder.add_variable(d); + + builder.create_big_add_gate({ a_idx, b_idx, c_idx, d_idx, FF(1), FF(1), FF(1), FF(-1), FF(0) }); + } + } + + /** + * @brief Mock the interactions of a simple curcuit with the op_queue + * @todo The transcript aggregation protocol in the Goblin proof system can not yet support an empty "previous + * transcript" (see issue #723) because the corresponding commitments are zero / the point at infinity. This + * function mocks the interactions with the op queue of a fictional "first" circuit. This way, when we go to + * generate a proof over our first "real" circuit, the transcript aggregation protocol can proceed nominally. The + * mock data is valid in the sense that it can be processed by all stages of Goblin as if it came from a genuine + * circuit. + * + * + * @param op_queue + */ + static void perform_op_queue_interactions_for_mock_first_circuit( + std::shared_ptr& op_queue) + { + proof_system::GoblinUltraCircuitBuilder builder{ op_queue }; + + // Add a mul accum op and an equality op + auto point = Point::one() * FF::random_element(); + auto scalar = FF::random_element(); + builder.queue_ecc_mul_accum(point, scalar); + builder.queue_ecc_eq(); + + op_queue->set_size_data(); + + // Manually compute the op queue transcript commitments (which would normally be done by the merge prover) + auto crs_factory_ = barretenberg::srs::get_crs_factory(); + auto commitment_key = CommitmentKey(op_queue->get_current_size(), crs_factory_); + std::array op_queue_commitments; + size_t idx = 0; + for (auto& entry : op_queue->get_aggregate_transcript()) { + op_queue_commitments[idx++] = commitment_key.commit(entry); + } + // Store the commitment data for use by the prover of the next circuit + op_queue->set_commitment_data(op_queue_commitments); + } + + /** + * @brief Generate a simple test circuit with some ECC op gates and conventional arithmetic gates + * + * @param builder + */ + static void construct_simple_initial_circuit(GoblinUltraBuilder& builder) + { + // TODO(https://github.com/AztecProtocol/barretenberg/issues/800) Testing cleanup + perform_op_queue_interactions_for_mock_first_circuit(builder.op_queue); + + // Add some arbitrary ecc op gates + for (size_t i = 0; i < 3; ++i) { + auto point = Point::random_element(); + auto scalar = FF::random_element(); + builder.queue_ecc_add_accum(point); + builder.queue_ecc_mul_accum(point, scalar); + } + // queues the result of the preceding ECC + builder.queue_ecc_eq(); // should be eq and reset + + construct_arithmetic_circuit(builder); + } +}; +} // namespace barretenberg \ No newline at end of file diff --git a/cpp/src/barretenberg/proof_system/circuit_builder/goblin_translator_circuit_builder.hpp b/cpp/src/barretenberg/proof_system/circuit_builder/goblin_translator_circuit_builder.hpp index 92164873e4..9527e5283d 100644 --- a/cpp/src/barretenberg/proof_system/circuit_builder/goblin_translator_circuit_builder.hpp +++ b/cpp/src/barretenberg/proof_system/circuit_builder/goblin_translator_circuit_builder.hpp @@ -367,6 +367,7 @@ class GoblinTranslatorCircuitBuilder : public CircuitBuilderBase class ProtoGalaxyProver_ { num_threads = num_threads > 0 ? num_threads : 1; // ensure num threads is >= 1 size_t iterations_per_thread = common_circuit_size / num_threads; // actual iterations per thread - // Constuct univariate accumulator containers; one per thread + // Construct univariate accumulator containers; one per thread std::vector thread_univariate_accumulators(num_threads); for (auto& accum : thread_univariate_accumulators) { Utils::zero_univariates(accum); } - // Constuct extended univariates containers; one per thread + // Construct extended univariates containers; one per thread std::vector extended_univariates; extended_univariates.resize(num_threads); diff --git a/cpp/src/barretenberg/stdlib/recursion/CMakeLists.txt b/cpp/src/barretenberg/stdlib/recursion/CMakeLists.txt index 7ba574b260..3b7a634c74 100644 --- a/cpp/src/barretenberg/stdlib/recursion/CMakeLists.txt +++ b/cpp/src/barretenberg/stdlib/recursion/CMakeLists.txt @@ -1 +1 @@ -barretenberg_module(stdlib_recursion ecc proof_system stdlib_primitives stdlib_pedersen_commitment stdlib_blake3s ultra_honk) \ No newline at end of file +barretenberg_module(stdlib_recursion ecc proof_system stdlib_primitives stdlib_pedersen_commitment stdlib_blake3s ultra_honk eccvm translator_vm) \ No newline at end of file diff --git a/cpp/src/barretenberg/stdlib/recursion/goblin/full_goblin_recursion.test.cpp b/cpp/src/barretenberg/stdlib/recursion/goblin/full_goblin_recursion.test.cpp new file mode 100644 index 0000000000..51d9f1943c --- /dev/null +++ b/cpp/src/barretenberg/stdlib/recursion/goblin/full_goblin_recursion.test.cpp @@ -0,0 +1,96 @@ +#include "barretenberg/eccvm/eccvm_composer.hpp" +#include "barretenberg/goblin/goblin.hpp" +#include "barretenberg/goblin/mock_circuits.hpp" +#include "barretenberg/goblin/translation_evaluations.hpp" +#include "barretenberg/proof_system/circuit_builder/eccvm/eccvm_circuit_builder.hpp" +#include "barretenberg/proof_system/circuit_builder/goblin_ultra_circuit_builder.hpp" +#include "barretenberg/proof_system/circuit_builder/ultra_circuit_builder.hpp" +#include "barretenberg/stdlib/recursion/honk/verifier/ultra_recursive_verifier.hpp" +#include "barretenberg/translator_vm/goblin_translator_composer.hpp" +#include "barretenberg/ultra_honk/ultra_composer.hpp" + +#include + +using namespace proof_system::honk; +namespace goblin_recursion_tests { + +class GoblinRecursionTests : public ::testing::Test { + protected: + static void SetUpTestSuite() + { + barretenberg::srs::init_crs_factory("../srs_db/ignition"); + barretenberg::srs::init_grumpkin_crs_factory("../srs_db/grumpkin"); + } + + using Curve = curve::BN254; + using FF = Curve::ScalarField; + using Fbase = Curve::BaseField; + using Point = Curve::AffineElement; + using CommitmentKey = pcs::CommitmentKey; + using OpQueue = proof_system::ECCOpQueue; + using GoblinUltraBuilder = proof_system::GoblinUltraCircuitBuilder; + using ECCVMFlavor = flavor::ECCVM; + using ECCVMBuilder = proof_system::ECCVMCircuitBuilder; + using ECCVMComposer = ECCVMComposer_; + using TranslatorFlavor = flavor::GoblinTranslator; + using TranslatorBuilder = proof_system::GoblinTranslatorCircuitBuilder; + using TranslatorComposer = GoblinTranslatorComposer; + using TranslatorConsistencyData = barretenberg::TranslationEvaluations; + using Proof = proof_system::plonk::proof; + using NativeVerificationKey = flavor::GoblinUltra::VerificationKey; + using RecursiveFlavor = flavor::GoblinUltraRecursive_; + using RecursiveVerifier = proof_system::plonk::stdlib::recursion::honk::UltraRecursiveVerifier_; + using KernelInput = Goblin::AccumulationOutput; + + /** + * @brief Construct a mock kernel circuit + * @details This circuit contains (1) some basic/arbitrary arithmetic gates, (2) a genuine recursive verification of + * the proof provided as input. It does not contain any other real kernel logic. + * + * @param builder + * @param kernel_input A proof to be recursively verified and the corresponding native verification key + */ + static void construct_mock_kernel_circuit(GoblinUltraBuilder& builder, KernelInput& kernel_input) + { + // Generic operations e.g. state updates (just arith gates for now) + GoblinTestingUtils::construct_arithmetic_circuit(builder); + + // Execute recursive aggregation of previous kernel proof + RecursiveVerifier verifier{ &builder, kernel_input.verification_key }; + // TODO(https://github.com/AztecProtocol/barretenberg/issues/801): Aggregation + auto pairing_points = verifier.verify_proof(kernel_input.proof); // app function proof + pairing_points = verifier.verify_proof(kernel_input.proof); // previous kernel proof + } +}; + +/** + * @brief A full Goblin test that mimicks the basic aztec client architecture + * + */ +TEST_F(GoblinRecursionTests, Pseudo) +{ + barretenberg::Goblin goblin; + + // Construct an initial circuit; its proof will be recursively verified by the first kernel + GoblinUltraBuilder initial_circuit{ goblin.op_queue }; + GoblinTestingUtils::construct_simple_initial_circuit(initial_circuit); + KernelInput kernel_input = goblin.accumulate(initial_circuit); + + // Construct a series of simple Goblin circuits; generate and verify their proofs + size_t NUM_CIRCUITS = 2; + for (size_t circuit_idx = 0; circuit_idx < NUM_CIRCUITS; ++circuit_idx) { + // Construct a circuit with logic resembling that of the "kernel circuit" + GoblinUltraBuilder circuit_builder{ goblin.op_queue }; + construct_mock_kernel_circuit(circuit_builder, kernel_input); + + // Construct proof of the current kernel circuit to be recursively verified by the next one + kernel_input = goblin.accumulate(circuit_builder); + } + + Goblin::Proof proof = goblin.prove(); + bool verified = goblin.verify(proof); + EXPECT_TRUE(verified); +} + +// TODO(https://github.com/AztecProtocol/barretenberg/issues/787) Expand these tests. +} // namespace goblin_recursion_tests diff --git a/cpp/src/barretenberg/stdlib/recursion/honk/verifier/ultra_recursive_verifier.cpp b/cpp/src/barretenberg/stdlib/recursion/honk/verifier/ultra_recursive_verifier.cpp index 785f2c8331..6c303d15e2 100644 --- a/cpp/src/barretenberg/stdlib/recursion/honk/verifier/ultra_recursive_verifier.cpp +++ b/cpp/src/barretenberg/stdlib/recursion/honk/verifier/ultra_recursive_verifier.cpp @@ -8,8 +8,8 @@ namespace proof_system::plonk::stdlib::recursion::honk { template -UltraRecursiveVerifier_::UltraRecursiveVerifier_(Builder* builder, - std::shared_ptr native_verifier_key) +UltraRecursiveVerifier_::UltraRecursiveVerifier_( + Builder* builder, const std::shared_ptr& native_verifier_key) : key(std::make_shared(builder, native_verifier_key)) , builder(builder) {} diff --git a/cpp/src/barretenberg/stdlib/recursion/honk/verifier/ultra_recursive_verifier.hpp b/cpp/src/barretenberg/stdlib/recursion/honk/verifier/ultra_recursive_verifier.hpp index 67b5411025..62803746d5 100644 --- a/cpp/src/barretenberg/stdlib/recursion/honk/verifier/ultra_recursive_verifier.hpp +++ b/cpp/src/barretenberg/stdlib/recursion/honk/verifier/ultra_recursive_verifier.hpp @@ -19,7 +19,8 @@ template class UltraRecursiveVerifier_ { using Builder = typename Flavor::CircuitBuilder; using PairingPoints = std::array; - explicit UltraRecursiveVerifier_(Builder* builder, std::shared_ptr native_verifier_key); + explicit UltraRecursiveVerifier_(Builder* builder, + const std::shared_ptr& native_verifier_key); UltraRecursiveVerifier_(UltraRecursiveVerifier_&& other) = delete; UltraRecursiveVerifier_(const UltraRecursiveVerifier_& other) = delete; UltraRecursiveVerifier_& operator=(const UltraRecursiveVerifier_& other) = delete; diff --git a/cpp/src/barretenberg/sumcheck/sumcheck_round.hpp b/cpp/src/barretenberg/sumcheck/sumcheck_round.hpp index b5c366be75..7ec78a7379 100644 --- a/cpp/src/barretenberg/sumcheck/sumcheck_round.hpp +++ b/cpp/src/barretenberg/sumcheck/sumcheck_round.hpp @@ -123,13 +123,13 @@ template class SumcheckProverRound { barretenberg::thread_utils::calculate_num_threads_pow2(round_size, min_iterations_per_thread); size_t iterations_per_thread = round_size / num_threads; // actual iterations per thread - // Constuct univariate accumulator containers; one per thread + // Construct univariate accumulator containers; one per thread std::vector thread_univariate_accumulators(num_threads); for (auto& accum : thread_univariate_accumulators) { Utils::zero_univariates(accum); } - // Constuct extended edge containers; one per thread + // Construct extended edge containers; one per thread std::vector extended_edges; extended_edges.resize(num_threads); diff --git a/cpp/src/barretenberg/transcript/transcript.hpp b/cpp/src/barretenberg/transcript/transcript.hpp index 018001ec10..e734829460 100644 --- a/cpp/src/barretenberg/transcript/transcript.hpp +++ b/cpp/src/barretenberg/transcript/transcript.hpp @@ -4,8 +4,16 @@ #include "barretenberg/crypto/blake3s/blake3s.hpp" #include "barretenberg/crypto/pedersen_hash/pedersen.hpp" +// #define LOG_CHALLENGES +// #define LOG_INTERACTIONS + namespace proof_system::honk { +template +concept Loggable = (std::same_as || std::same_as || + std::same_as || + std::same_as || std::same_as); + // class TranscriptManifest; class TranscriptManifest { struct RoundData { @@ -268,6 +276,11 @@ class BaseTranscript { auto element_bytes = to_buffer(element); proof_data.insert(proof_data.end(), element_bytes.begin(), element_bytes.end()); +#ifdef LOG_INTERACTIONS + if constexpr (Loggable) { + info("sent: ", label, ": ", element); + } +#endif BaseTranscript::consume_prover_element_bytes(label, element_bytes); } @@ -289,6 +302,11 @@ class BaseTranscript { T element = from_buffer(element_bytes); +#ifdef LOG_INTERACTIONS + if constexpr (Loggable) { + info("received: ", label, ": ", element); + } +#endif return element; } @@ -320,7 +338,14 @@ class BaseTranscript { return verifier_transcript; }; - uint256_t get_challenge(const std::string& label) { return get_challenges(label)[0]; } + uint256_t get_challenge(const std::string& label) + { + uint256_t result = get_challenges(label)[0]; +#if defined LOG_CHALLENGES || defined LOG_INTERACTIONS + info("challenge: ", label, ": ", result); +#endif + return result; + } [[nodiscard]] TranscriptManifest get_manifest() const { return manifest; }; diff --git a/cpp/src/barretenberg/translator_vm/goblin_translator_composer.test.cpp b/cpp/src/barretenberg/translator_vm/goblin_translator_composer.test.cpp index ed7c1274d5..d186ebc6eb 100644 --- a/cpp/src/barretenberg/translator_vm/goblin_translator_composer.test.cpp +++ b/cpp/src/barretenberg/translator_vm/goblin_translator_composer.test.cpp @@ -9,6 +9,9 @@ #include using namespace proof_system::honk; +using CircuitBuilder = flavor::GoblinTranslator::CircuitBuilder; +using Transcript = flavor::GoblinTranslator::Transcript; +using OpQueue = proof_system::ECCOpQueue; namespace test_goblin_translator_composer { @@ -25,15 +28,6 @@ std::vector add_variables(auto& circuit_constructor, std::vector(); @@ -68,14 +62,24 @@ TEST_F(GoblinTranslatorComposerTests, Basic) op_queue->add_accumulate(P1); op_queue->mul_accumulate(P2, z); } - Fq batching_challenge = Fq::random_element(); - Fq x = Fq::random_element(); - auto circuit_builder = proof_system::GoblinTranslatorCircuitBuilder(batching_challenge, x); - circuit_builder.feed_ecc_op_queue_into_circuit(op_queue); + + auto prover_transcript = std::make_shared(); + prover_transcript->send_to_verifier("init", Fq::random_element()); + prover_transcript->export_proof(); + Fq translation_batching_challenge = prover_transcript->get_challenge("Translation:batching_challenge"); + Fq translation_evaluation_challenge = Fq::random_element(); + auto circuit_builder = CircuitBuilder(translation_batching_challenge, translation_evaluation_challenge, op_queue); EXPECT_TRUE(circuit_builder.check_circuit()); auto composer = GoblinTranslatorComposer(); - prove_and_verify(circuit_builder, composer, /*expected_result=*/true); + auto prover = composer.create_prover(circuit_builder, prover_transcript); + auto proof = prover.construct_proof(); + + auto verifier_transcript = std::make_shared(prover_transcript->proof_data); + verifier_transcript->template receive_from_prover("init"); + auto verifier = composer.create_verifier(circuit_builder, verifier_transcript); + bool verified = verifier.verify_proof(proof); + EXPECT_TRUE(verified); } } // namespace test_goblin_translator_composer diff --git a/cpp/src/barretenberg/translator_vm/goblin_translator_prover.cpp b/cpp/src/barretenberg/translator_vm/goblin_translator_prover.cpp index cd3e010b0d..aeaa9a2719 100644 --- a/cpp/src/barretenberg/translator_vm/goblin_translator_prover.cpp +++ b/cpp/src/barretenberg/translator_vm/goblin_translator_prover.cpp @@ -256,13 +256,12 @@ void GoblinTranslatorProver::execute_preamble_round() const auto SHIFT = uint256_t(1) << Flavor::NUM_LIMB_BITS; const auto SHIFTx2 = uint256_t(1) << (Flavor::NUM_LIMB_BITS * 2); const auto SHIFTx3 = uint256_t(1) << (Flavor::NUM_LIMB_BITS * 3); - const auto accumulated_result = typename Flavor::BF(uint256_t(key->accumulators_binary_limbs_0[1]) + - uint256_t(key->accumulators_binary_limbs_1[1]) * SHIFT + - uint256_t(key->accumulators_binary_limbs_2[1]) * SHIFTx2 + - uint256_t(key->accumulators_binary_limbs_3[1]) * SHIFTx3); + const auto accumulated_result = + BF(uint256_t(key->accumulators_binary_limbs_0[1]) + uint256_t(key->accumulators_binary_limbs_1[1]) * SHIFT + + uint256_t(key->accumulators_binary_limbs_2[1]) * SHIFTx2 + + uint256_t(key->accumulators_binary_limbs_3[1]) * SHIFTx3); transcript->send_to_verifier("circuit_size", circuit_size); transcript->send_to_verifier("evaluation_input_x", key->evaluation_input_x); - transcript->send_to_verifier("batching_challenge_v", key->batching_challenge_v); transcript->send_to_verifier("accumulated_result", accumulated_result); } @@ -366,7 +365,7 @@ void GoblinTranslatorProver::execute_zeromorph_rounds() plonk::proof& GoblinTranslatorProver::export_proof() { - proof.proof_data = transcript->proof_data; + proof.proof_data = transcript->export_proof(); return proof; } diff --git a/cpp/src/barretenberg/translator_vm/goblin_translator_prover.hpp b/cpp/src/barretenberg/translator_vm/goblin_translator_prover.hpp index 3a2db24407..99bf48c490 100644 --- a/cpp/src/barretenberg/translator_vm/goblin_translator_prover.hpp +++ b/cpp/src/barretenberg/translator_vm/goblin_translator_prover.hpp @@ -12,6 +12,7 @@ class GoblinTranslatorProver { using Flavor = honk::flavor::GoblinTranslator; using FF = typename Flavor::FF; + using BF = typename Flavor::BF; using Commitment = typename Flavor::Commitment; using CommitmentKey = typename Flavor::CommitmentKey; using ProvingKey = typename Flavor::ProvingKey; diff --git a/cpp/src/barretenberg/translator_vm/goblin_translator_verifier.cpp b/cpp/src/barretenberg/translator_vm/goblin_translator_verifier.cpp index 089c2c3745..44e736b71d 100644 --- a/cpp/src/barretenberg/translator_vm/goblin_translator_verifier.cpp +++ b/cpp/src/barretenberg/translator_vm/goblin_translator_verifier.cpp @@ -68,9 +68,8 @@ void GoblinTranslatorVerifier::put_translation_data_in_relation_parameters(const */ bool GoblinTranslatorVerifier::verify_proof(const plonk::proof& proof) { - transcript = std::make_shared(proof.proof_data); - - transcript = std::make_shared(proof.proof_data); + batching_challenge_v = transcript->get_challenge("Translation:batching_challenge"); + transcript->load_proof(proof.proof_data); Flavor::VerifierCommitments commitments{ key }; Flavor::CommitmentLabels commitment_labels; @@ -78,7 +77,6 @@ bool GoblinTranslatorVerifier::verify_proof(const plonk::proof& proof) // TODO(Adrian): Change the initialization of the transcript to take the VK hash? const auto circuit_size = transcript->template receive_from_prover("circuit_size"); evaluation_input_x = transcript->template receive_from_prover("evaluation_input_x"); - batching_challenge_v = transcript->template receive_from_prover("batching_challenge_v"); const BF accumulated_result = transcript->template receive_from_prover("accumulated_result"); diff --git a/cpp/src/barretenberg/ultra_honk/merge_prover.cpp b/cpp/src/barretenberg/ultra_honk/merge_prover.cpp index a27b2091f3..671634a307 100644 --- a/cpp/src/barretenberg/ultra_honk/merge_prover.cpp +++ b/cpp/src/barretenberg/ultra_honk/merge_prover.cpp @@ -94,7 +94,7 @@ template plonk::proof& MergeProver_::construct_proof() FF alpha = transcript->get_challenge("alpha"); - // Constuct batched polynomial to opened via KZG + // Construct batched polynomial to opened via KZG auto batched_polynomial = Polynomial(N); auto batched_eval = FF(0); auto alpha_pow = FF(1); diff --git a/cpp/src/barretenberg/ultra_honk/merge_verifier.cpp b/cpp/src/barretenberg/ultra_honk/merge_verifier.cpp index 1d913b342f..c93257af7d 100644 --- a/cpp/src/barretenberg/ultra_honk/merge_verifier.cpp +++ b/cpp/src/barretenberg/ultra_honk/merge_verifier.cpp @@ -62,7 +62,7 @@ template bool MergeVerifier_::verify_proof(const plonk FF alpha = transcript->get_challenge("alpha"); - // Constuct batched commitment and evaluation from constituents + // Construct batched commitment and evaluation from constituents auto batched_commitment = opening_claims[0].commitment; auto batched_eval = opening_claims[0].opening_pair.evaluation; auto alpha_pow = alpha;