From 970bb073763cc59552cd05dccf7f8fc63f58cef9 Mon Sep 17 00:00:00 2001 From: ledwards2225 <98505400+ledwards2225@users.noreply.github.com> Date: Thu, 27 Jul 2023 07:49:18 -0700 Subject: [PATCH] feat: Goblin Honk Composer/Prover/Verifier (#1220) # Description The Composer, Prover and Verifier for Goblin Ultra Honk. These are implemented as instantiations of the existing "Ultra" template classes, e.g. `GoblinUltraComposer = UltraComposer_`, etc. This work makes it possible to create and verify a Goblin Ultra Honk proof. "Verification" here means confirmation that the ECC op gates have been correctly incorporated into the circuit (checked via a new relation) plus genuine verification of the conventional UltraHonk proof. The _correctness_ of the the ECC op gates is of course not verified, as that is the role of the other components of the Goblin stack. The new "op gate consistency" relation simply checks that the op wire values have been correctly copied into the conventional wires (needed for copy constraints) and that the op wire polynomials vanish everywhere outside the range of ECC op gates. # Checklist: - [ ] I have reviewed my diff in github, line by line. - [ ] Every change is related to the PR description. - [ ] I have [linked](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) this pull request to the issue(s) that it resolves. - [ ] There are no unexpected formatting changes, superfluous debug logs, or commented-out code. - [ ] The branch has been merged or rebased against the head of its merge target. - [ ] I'm happy for the PR to be merged at the reviewer's next convenience. --------- Co-authored-by: ludamad --- .../composer/goblin_ultra_composer.test.cpp | 65 +++ .../honk/composer/ultra_composer.cpp | 107 +++-- .../honk/composer/ultra_composer.hpp | 8 +- .../barretenberg/honk/flavor/goblin_ultra.hpp | 442 ++++++++++++++++++ .../honk/proof_system/prover_library.cpp | 15 +- .../honk/proof_system/ultra_prover.cpp | 47 +- .../honk/proof_system/ultra_prover.hpp | 5 +- .../honk/proof_system/ultra_verifier.cpp | 19 +- .../honk/proof_system/ultra_verifier.hpp | 2 + .../relations/ecc_op_queue_relation.hpp | 116 +++++ .../relations/relation_correctness.test.cpp | 394 +++++++++++----- .../honk/sumcheck/sumcheck.test.cpp | 5 +- .../honk/utils/grand_product_delta.hpp | 11 +- .../circuit_builder/ultra_circuit_builder.cpp | 5 +- .../proof_system/composer/composer_lib.hpp | 76 ++- .../proof_system/composer/permutation_lib.hpp | 45 +- .../proof_system/flavor/flavor.hpp | 12 +- 17 files changed, 1158 insertions(+), 216 deletions(-) create mode 100644 cpp/src/barretenberg/honk/composer/goblin_ultra_composer.test.cpp create mode 100644 cpp/src/barretenberg/honk/flavor/goblin_ultra.hpp create mode 100644 cpp/src/barretenberg/honk/sumcheck/relations/ecc_op_queue_relation.hpp diff --git a/cpp/src/barretenberg/honk/composer/goblin_ultra_composer.test.cpp b/cpp/src/barretenberg/honk/composer/goblin_ultra_composer.test.cpp new file mode 100644 index 000000000..ff4249f37 --- /dev/null +++ b/cpp/src/barretenberg/honk/composer/goblin_ultra_composer.test.cpp @@ -0,0 +1,65 @@ +#include +#include +#include + +#include "barretenberg/common/log.hpp" +#include "barretenberg/honk/composer/ultra_composer.hpp" +#include "barretenberg/honk/proof_system/ultra_prover.hpp" +#include "barretenberg/proof_system/circuit_builder/ultra_circuit_builder.hpp" + +using namespace proof_system::honk; + +namespace test_ultra_honk_composer { + +namespace { +auto& engine = numeric::random::get_debug_engine(); +} + +class GoblinUltraHonkComposerTests : public ::testing::Test { + protected: + static void SetUpTestSuite() { barretenberg::srs::init_crs_factory("../srs_db/ignition"); } +}; + +/** + * @brief Test proof construction/verification for a circuit with ECC op gates, public inputs, and basic arithmetic + * gates + * + */ +TEST_F(GoblinUltraHonkComposerTests, SimpleCircuit) +{ + auto builder = UltraCircuitBuilder(); + + // Define an arbitrary number of operations/gates + size_t num_ecc_ops = 3; + size_t num_conventional_gates = 10; + + // Add some ecc op gates + for (size_t i = 0; i < num_ecc_ops; ++i) { + auto point = g1::affine_one * fr::random_element(); + auto scalar = fr::random_element(); + builder.queue_ecc_mul_accum(point, scalar); + } + + // Add some conventional gates that utlize public inputs + for (size_t i = 0; i < num_conventional_gates; ++i) { + fr a = fr::random_element(); + fr b = fr::random_element(); + fr c = fr::random_element(); + fr 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, fr(1), fr(1), fr(1), fr(-1), fr(0) }); + } + + auto composer = GoblinUltraComposer(); + auto prover = composer.create_prover(builder); + auto verifier = composer.create_verifier(builder); + auto proof = prover.construct_proof(); + bool verified = verifier.verify_proof(proof); + EXPECT_EQ(verified, true); +} + +} // namespace test_ultra_honk_composer diff --git a/cpp/src/barretenberg/honk/composer/ultra_composer.cpp b/cpp/src/barretenberg/honk/composer/ultra_composer.cpp index 0a21f5ac1..2fb011861 100644 --- a/cpp/src/barretenberg/honk/composer/ultra_composer.cpp +++ b/cpp/src/barretenberg/honk/composer/ultra_composer.cpp @@ -21,14 +21,16 @@ void UltraComposer_::compute_circuit_size_parameters(CircuitBuilder& cir lookups_size += table.lookup_gates.size(); } + // Get num conventional gates, num public inputs and num Goblin style ECC op gates + const size_t num_gates = circuit_constructor.num_gates; num_public_inputs = circuit_constructor.public_inputs.size(); + num_ecc_op_gates = circuit_constructor.num_ecc_op_gates; // minimum circuit size due to the length of lookups plus tables - const size_t minimum_circuit_size_due_to_lookups = tables_size + lookups_size + zero_row_offset; + const size_t minimum_circuit_size_due_to_lookups = tables_size + lookups_size + num_zero_rows; // number of populated rows in the execution trace - const size_t num_rows_populated_in_execution_trace = - circuit_constructor.num_gates + circuit_constructor.public_inputs.size() + zero_row_offset; + size_t num_rows_populated_in_execution_trace = num_zero_rows + num_ecc_op_gates + num_public_inputs + num_gates; // The number of gates is max(lookup gates + tables, rows already populated in trace) + 1, where the +1 is due to // addition of a "zero row" at top of the execution trace to ensure wires and other polys are shiftable. @@ -48,15 +50,7 @@ template void UltraComposer_::compute_witness(Circu return; } - // At this point, the wires have been populated with as many values as rows in the execution trace. We need to pad - // with zeros up to the full length, i.e. total_num_gates = already populated rows of execution trace + tables_size. - for (size_t i = 0; i < tables_size; ++i) { - circuit_constructor.w_l.emplace_back(circuit_constructor.zero_idx); - circuit_constructor.w_r.emplace_back(circuit_constructor.zero_idx); - circuit_constructor.w_o.emplace_back(circuit_constructor.zero_idx); - circuit_constructor.w_4.emplace_back(circuit_constructor.zero_idx); - } - + // Construct the conventional wire polynomials auto wire_polynomials = construct_wire_polynomials_base(circuit_constructor, dyadic_circuit_size); proving_key->w_l = wire_polynomials[0]; @@ -64,24 +58,23 @@ template void UltraComposer_::compute_witness(Circu proving_key->w_o = wire_polynomials[2]; proving_key->w_4 = wire_polynomials[3]; + // If Goblin, construct the ECC op queue wire polynomials + if constexpr (IsGoblinFlavor) { + construct_ecc_op_wire_polynomials(wire_polynomials); + } + + // Construct the sorted concatenated list polynomials for the lookup argument polynomial s_1(dyadic_circuit_size); polynomial s_2(dyadic_circuit_size); polynomial s_3(dyadic_circuit_size); polynomial s_4(dyadic_circuit_size); - // TODO(luke): The +1 size for z_lookup is not necessary and can lead to confusion. Resolve. - polynomial z_lookup(dyadic_circuit_size + 1); // Only instantiated in this function; nothing assigned. - - // TODO(kesha): Look at this once we figure out how we do ZK (previously we had roots cut out, so just added - // randomness) - // The size of empty space in sorted polynomials - size_t count = dyadic_circuit_size - tables_size - lookups_size; - ASSERT(count > 0); // We need at least 1 row of zeroes for the permutation argument - for (size_t i = 0; i < count; ++i) { - s_1[i] = 0; - s_2[i] = 0; - s_3[i] = 0; - s_4[i] = 0; - } + + // The sorted list polynomials have (tables_size + lookups_size) populated entries. We define the index below so + // that these entries are written into the last indices of the polynomials. The values on the first + // dyadic_circuit_size - (tables_size + lookups_size) indices are automatically initialized to zero via the + // polynomial constructor. + size_t s_index = dyadic_circuit_size - tables_size - lookups_size; + ASSERT(s_index > 0); // We need at least 1 row of zeroes for the permutation argument for (auto& table : circuit_constructor.lookup_tables) { const fr table_index(table.table_index); @@ -120,11 +113,11 @@ template void UltraComposer_::compute_witness(Circu for (const auto& entry : lookup_gates) { const auto components = entry.to_sorted_list_components(table.use_twin_keys); - s_1[count] = components[0]; - s_2[count] = components[1]; - s_3[count] = components[2]; - s_4[count] = table_index; - ++count; + s_1[s_index] = components[0]; + s_2[s_index] = components[1]; + s_3[s_index] = components[2]; + s_4[s_index] = table_index; + ++s_index; } } @@ -137,11 +130,10 @@ template void UltraComposer_::compute_witness(Circu // Copy memory read/write record data into proving key. Prover needs to know which gates contain a read/write // 'record' witness on the 4th wire. This wire value can only be fully computed once the first 3 wire polynomials // have been committed to. The 4th wire on these gates will be a random linear combination of the first 3 wires, - // using the plookup challenge `eta`. We need to update the records with an offset Because we shift the gates by - // the number of public inputs plus an additional offset for a zero row. - auto add_public_inputs_offset = [this](uint32_t gate_index) { - return gate_index + num_public_inputs + zero_row_offset; - }; + // using the plookup challenge `eta`. We need to update the records with an offset Because we shift the gates to + // account for everything that comes before them in the execution trace, e.g. public inputs, a zero row, etc. + size_t offset = num_ecc_op_gates + num_public_inputs + num_zero_rows; + auto add_public_inputs_offset = [offset](uint32_t gate_index) { return gate_index + offset; }; proving_key->memory_read_records = std::vector(); proving_key->memory_write_records = std::vector(); @@ -157,6 +149,38 @@ template void UltraComposer_::compute_witness(Circu computed_witness = true; } +/** + * @brief Construct Goblin style ECC op wire polynomials + * @details The Ecc op wire values are assumed to have already been stored in the corresponding block of the + * conventional wire polynomials. The values for the ecc op wire polynomials are set based on those values. + * + * @tparam Flavor + * @param wire_polynomials + */ +template void UltraComposer_::construct_ecc_op_wire_polynomials(auto& wire_polynomials) +{ + std::array op_wire_polynomials; + for (auto& poly : op_wire_polynomials) { + poly = polynomial(dyadic_circuit_size); + } + + // The ECC op wires are constructed to contain the op data on the appropriate range and to vanish everywhere else. + // The op data is assumed to have already been stored at the correct location in the convetional wires so the data + // can simply be copied over directly. + const size_t op_wire_offset = Flavor::has_zero_row ? 1 : 0; + for (size_t poly_idx = 0; poly_idx < Flavor::NUM_WIRES; ++poly_idx) { + for (size_t i = 0; i < num_ecc_op_gates; ++i) { + size_t idx = i + op_wire_offset; + op_wire_polynomials[poly_idx][idx] = wire_polynomials[poly_idx][idx]; + } + } + + proving_key->ecc_op_wire_1 = op_wire_polynomials[0]; + proving_key->ecc_op_wire_2 = op_wire_polynomials[1]; + proving_key->ecc_op_wire_3 = op_wire_polynomials[2]; + proving_key->ecc_op_wire_4 = op_wire_polynomials[3]; +} + template UltraProver_ UltraComposer_::create_prover(CircuitBuilder& circuit_constructor) { @@ -258,6 +282,10 @@ std::shared_ptr UltraComposer_::compute_pro proving_key->contains_recursive_proof = contains_recursive_proof; + if constexpr (IsGoblinFlavor) { + proving_key->num_ecc_op_gates = num_ecc_op_gates; + } + return proving_key; } @@ -308,6 +336,12 @@ std::shared_ptr UltraComposer_::comput verification_key->lagrange_first = commitment_key->commit(proving_key->lagrange_first); verification_key->lagrange_last = commitment_key->commit(proving_key->lagrange_last); + // TODO(luke): Similar to the lagrange_first/last polynomials, we dont really need to commit to this polynomial due + // to its simple structure. Handling it in the same way as the lagrange polys for now for simplicity. + if constexpr (IsGoblinFlavor) { + verification_key->lagrange_ecc_op = commitment_key->commit(proving_key->lagrange_ecc_op); + } + // // See `add_recusrive_proof()` for how this recursive data is assigned. // verification_key->recursive_proof_public_input_indices = // std::vector(recursive_proof_public_input_indices.begin(), @@ -319,5 +353,6 @@ std::shared_ptr UltraComposer_::comput } template class UltraComposer_; template class UltraComposer_; +template class UltraComposer_; } // namespace proof_system::honk diff --git a/cpp/src/barretenberg/honk/composer/ultra_composer.hpp b/cpp/src/barretenberg/honk/composer/ultra_composer.hpp index 7028d2e10..c58cffec6 100644 --- a/cpp/src/barretenberg/honk/composer/ultra_composer.hpp +++ b/cpp/src/barretenberg/honk/composer/ultra_composer.hpp @@ -3,6 +3,7 @@ #include "barretenberg/honk/proof_system/ultra_prover.hpp" #include "barretenberg/honk/proof_system/ultra_verifier.hpp" #include "barretenberg/proof_system/composer/composer_lib.hpp" +#include "barretenberg/proof_system/flavor/flavor.hpp" #include "barretenberg/srs/factories/file_crs_factory.hpp" #include @@ -22,7 +23,7 @@ template class UltraComposer_ { using PCSVerificationKey = typename PCSParams::VerificationKey; // offset due to placing zero wires at the start of execution trace - static constexpr size_t zero_row_offset = Flavor::has_zero_row ? 1 : 0; + static constexpr size_t num_zero_rows = Flavor::has_zero_row ? 1 : 0; static constexpr std::string_view NAME_STRING = "UltraHonk"; static constexpr size_t NUM_WIRES = CircuitBuilder::NUM_WIRES; @@ -43,6 +44,7 @@ template class UltraComposer_ { size_t lookups_size = 0; // total number of lookup gates size_t tables_size = 0; // total number of table entries size_t num_public_inputs = 0; + size_t num_ecc_op_gates = 0; UltraComposer_() : crs_factory_(barretenberg::srs::get_crs_factory()){}; @@ -69,6 +71,8 @@ template class UltraComposer_ { void compute_witness(CircuitBuilder& circuit_constructor); + void construct_ecc_op_wire_polynomials(auto&); + UltraProver_ create_prover(CircuitBuilder& circuit_constructor); UltraVerifier_ create_verifier(const CircuitBuilder& circuit_constructor); @@ -81,7 +85,9 @@ template class UltraComposer_ { }; extern template class UltraComposer_; extern template class UltraComposer_; +extern template class UltraComposer_; // TODO(#532): this pattern is weird; is this not instantiating the templates? using UltraComposer = UltraComposer_; using UltraGrumpkinComposer = UltraComposer_; +using GoblinUltraComposer = UltraComposer_; } // namespace proof_system::honk diff --git a/cpp/src/barretenberg/honk/flavor/goblin_ultra.hpp b/cpp/src/barretenberg/honk/flavor/goblin_ultra.hpp new file mode 100644 index 000000000..b46613675 --- /dev/null +++ b/cpp/src/barretenberg/honk/flavor/goblin_ultra.hpp @@ -0,0 +1,442 @@ +#pragma once +#include "barretenberg/ecc/curves/bn254/g1.hpp" +#include "barretenberg/honk/pcs/commitment_key.hpp" +#include "barretenberg/honk/pcs/kzg/kzg.hpp" +#include "barretenberg/honk/sumcheck/polynomials/barycentric_data.hpp" +#include "barretenberg/honk/sumcheck/polynomials/univariate.hpp" +#include "barretenberg/honk/sumcheck/relations/auxiliary_relation.hpp" +#include "barretenberg/honk/sumcheck/relations/ecc_op_queue_relation.hpp" +#include "barretenberg/honk/sumcheck/relations/elliptic_relation.hpp" +#include "barretenberg/honk/sumcheck/relations/gen_perm_sort_relation.hpp" +#include "barretenberg/honk/sumcheck/relations/lookup_relation.hpp" +#include "barretenberg/honk/sumcheck/relations/permutation_relation.hpp" +#include "barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation.hpp" +#include "barretenberg/honk/transcript/transcript.hpp" +#include "barretenberg/polynomials/evaluation_domain.hpp" +#include "barretenberg/polynomials/polynomial.hpp" +#include "barretenberg/proof_system/circuit_builder/ultra_circuit_builder.hpp" +#include "barretenberg/proof_system/flavor/flavor.hpp" +#include "barretenberg/srs/factories/crs_factory.hpp" +#include +#include +#include +#include +#include +#include + +namespace proof_system::honk::flavor { + +class GoblinUltra { + public: + using CircuitBuilder = UltraCircuitBuilder; + using FF = barretenberg::fr; + using Polynomial = barretenberg::Polynomial; + using PolynomialHandle = std::span; + using G1 = barretenberg::g1; + using GroupElement = G1::element; + using Commitment = G1::affine_element; + using CommitmentHandle = G1::affine_element; + using PCSParams = pcs::kzg::Params; + using PCS = pcs::kzg::KZG; + + static constexpr size_t NUM_WIRES = CircuitBuilder::NUM_WIRES; + // The number of multivariate polynomials on which a sumcheck prover sumcheck operates (including shifts). We often + // need containers of this size to hold related data, so we choose a name more agnostic than `NUM_POLYNOMIALS`. + // Note: this number does not include the individual sorted list polynomials. + static constexpr size_t NUM_ALL_ENTITIES = 48; // 43 (UH) + 4 op wires + 1 op wire "selector" + // The number of polynomials precomputed to describe a circuit and to aid a prover in constructing a satisfying + // assignment of witnesses. We again choose a neutral name. + static constexpr size_t NUM_PRECOMPUTED_ENTITIES = 26; // 25 (UH) + 1 op wire "selector" + // The total number of witness entities not including shifts. + static constexpr size_t NUM_WITNESS_ENTITIES = 15; // 11 (UH) + 4 op wires + + using GrandProductRelations = std::tuple, sumcheck::LookupRelation>; + + // define the tuple of Relations that comprise the Sumcheck relation + using Relations = std::tuple, + sumcheck::UltraPermutationRelation, + sumcheck::LookupRelation, + sumcheck::GenPermSortRelation, + sumcheck::EllipticRelation, + sumcheck::AuxiliaryRelation, + sumcheck::EccOpQueueRelation>; + + static constexpr size_t MAX_RELATION_LENGTH = get_max_relation_length(); + + // MAX_RANDOM_RELATION_LENGTH = algebraic degree of sumcheck relation *after* multiplying by the `pow_zeta` random + // polynomial e.g. For \sum(x) [A(x) * B(x) + C(x)] * PowZeta(X), relation length = 2 and random relation length = 3 + static constexpr size_t MAX_RANDOM_RELATION_LENGTH = MAX_RELATION_LENGTH + 1; + static constexpr size_t NUM_RELATIONS = std::tuple_size::value; + + // define the container for storing the univariate contribution from each relation in Sumcheck + using RelationUnivariates = decltype(create_relation_univariates_container()); + using RelationValues = decltype(create_relation_values_container()); + + // Whether or not the first row of the execution trace is reserved for 0s to enable shifts + static constexpr bool has_zero_row = true; + + private: + template + /** + * @brief A base class labelling precomputed entities and (ordered) subsets of interest. + * @details Used to build the proving key and verification key. + */ + class PrecomputedEntities : public PrecomputedEntities_ { + public: + DataType& q_m = std::get<0>(this->_data); + DataType& q_c = std::get<1>(this->_data); + DataType& q_l = std::get<2>(this->_data); + DataType& q_r = std::get<3>(this->_data); + DataType& q_o = std::get<4>(this->_data); + DataType& q_4 = std::get<5>(this->_data); + DataType& q_arith = std::get<6>(this->_data); + DataType& q_sort = std::get<7>(this->_data); + DataType& q_elliptic = std::get<8>(this->_data); + DataType& q_aux = std::get<9>(this->_data); + DataType& q_lookup = std::get<10>(this->_data); + DataType& sigma_1 = std::get<11>(this->_data); + DataType& sigma_2 = std::get<12>(this->_data); + DataType& sigma_3 = std::get<13>(this->_data); + DataType& sigma_4 = std::get<14>(this->_data); + DataType& id_1 = std::get<15>(this->_data); + DataType& id_2 = std::get<16>(this->_data); + DataType& id_3 = std::get<17>(this->_data); + DataType& id_4 = std::get<18>(this->_data); + DataType& table_1 = std::get<19>(this->_data); + DataType& table_2 = std::get<20>(this->_data); + DataType& table_3 = std::get<21>(this->_data); + DataType& table_4 = std::get<22>(this->_data); + DataType& lagrange_first = std::get<23>(this->_data); + DataType& lagrange_last = std::get<24>(this->_data); + DataType& lagrange_ecc_op = std::get<25>(this->_data); // indicator poly for ecc op gates + + static constexpr CircuitType CIRCUIT_TYPE = CircuitBuilder::CIRCUIT_TYPE; + + std::vector get_selectors() override + { + return { q_m, q_c, q_l, q_r, q_o, q_4, q_arith, q_sort, q_elliptic, q_aux, q_lookup }; + }; + std::vector get_sigma_polynomials() override { return { sigma_1, sigma_2, sigma_3, sigma_4 }; }; + std::vector get_id_polynomials() override { return { id_1, id_2, id_3, id_4 }; }; + + std::vector get_table_polynomials() { return { table_1, table_2, table_3, table_4 }; }; + }; + + /** + * @brief Container for all witness polynomials used/constructed by the prover. + * @details Shifts are not included here since they do not occupy their own memory. + */ + template + class WitnessEntities : public WitnessEntities_ { + public: + DataType& w_l = std::get<0>(this->_data); + DataType& w_r = std::get<1>(this->_data); + DataType& w_o = std::get<2>(this->_data); + DataType& w_4 = std::get<3>(this->_data); + DataType& sorted_1 = std::get<4>(this->_data); + DataType& sorted_2 = std::get<5>(this->_data); + DataType& sorted_3 = std::get<6>(this->_data); + DataType& sorted_4 = std::get<7>(this->_data); + DataType& sorted_accum = std::get<8>(this->_data); + DataType& z_perm = std::get<9>(this->_data); + DataType& z_lookup = std::get<10>(this->_data); + DataType& ecc_op_wire_1 = std::get<11>(this->_data); + DataType& ecc_op_wire_2 = std::get<12>(this->_data); + DataType& ecc_op_wire_3 = std::get<13>(this->_data); + DataType& ecc_op_wire_4 = std::get<14>(this->_data); + + std::vector get_wires() override { return { w_l, w_r, w_o, w_4 }; }; + std::vector get_ecc_op_wires() + { + return { ecc_op_wire_1, ecc_op_wire_2, ecc_op_wire_3, ecc_op_wire_4 }; + }; + // The sorted concatenations of table and witness data needed for plookup. + std::vector get_sorted_polynomials() { return { sorted_1, sorted_2, sorted_3, sorted_4 }; }; + }; + + /** + * @brief A base class labelling all entities (for instance, all of the polynomials used by the prover during + * sumcheck) in this Honk variant along with particular subsets of interest + * @details Used to build containers for: the prover's polynomial during sumcheck; the sumcheck's folded + * polynomials; the univariates consturcted during during sumcheck; the evaluations produced by sumcheck. + * + * Symbolically we have: AllEntities = PrecomputedEntities + WitnessEntities + "ShiftedEntities". It could be + * implemented as such, but we have this now. + */ + template + class AllEntities : public AllEntities_ { + public: + DataType& q_c = std::get<0>(this->_data); + DataType& q_l = std::get<1>(this->_data); + DataType& q_r = std::get<2>(this->_data); + DataType& q_o = std::get<3>(this->_data); + DataType& q_4 = std::get<4>(this->_data); + DataType& q_m = std::get<5>(this->_data); + DataType& q_arith = std::get<6>(this->_data); + DataType& q_sort = std::get<7>(this->_data); + DataType& q_elliptic = std::get<8>(this->_data); + DataType& q_aux = std::get<9>(this->_data); + DataType& q_lookup = std::get<10>(this->_data); + DataType& sigma_1 = std::get<11>(this->_data); + DataType& sigma_2 = std::get<12>(this->_data); + DataType& sigma_3 = std::get<13>(this->_data); + DataType& sigma_4 = std::get<14>(this->_data); + DataType& id_1 = std::get<15>(this->_data); + DataType& id_2 = std::get<16>(this->_data); + DataType& id_3 = std::get<17>(this->_data); + DataType& id_4 = std::get<18>(this->_data); + DataType& table_1 = std::get<19>(this->_data); + DataType& table_2 = std::get<20>(this->_data); + DataType& table_3 = std::get<21>(this->_data); + DataType& table_4 = std::get<22>(this->_data); + DataType& lagrange_first = std::get<23>(this->_data); + DataType& lagrange_last = std::get<24>(this->_data); + DataType& lagrange_ecc_op = std::get<25>(this->_data); + DataType& w_l = std::get<26>(this->_data); + DataType& w_r = std::get<27>(this->_data); + DataType& w_o = std::get<28>(this->_data); + DataType& w_4 = std::get<29>(this->_data); + DataType& sorted_accum = std::get<30>(this->_data); + DataType& z_perm = std::get<31>(this->_data); + DataType& z_lookup = std::get<32>(this->_data); + DataType& ecc_op_wire_1 = std::get<33>(this->_data); + DataType& ecc_op_wire_2 = std::get<34>(this->_data); + DataType& ecc_op_wire_3 = std::get<35>(this->_data); + DataType& ecc_op_wire_4 = std::get<36>(this->_data); + DataType& table_1_shift = std::get<37>(this->_data); + DataType& table_2_shift = std::get<38>(this->_data); + DataType& table_3_shift = std::get<39>(this->_data); + DataType& table_4_shift = std::get<40>(this->_data); + DataType& w_l_shift = std::get<41>(this->_data); + DataType& w_r_shift = std::get<42>(this->_data); + DataType& w_o_shift = std::get<43>(this->_data); + DataType& w_4_shift = std::get<44>(this->_data); + DataType& sorted_accum_shift = std::get<45>(this->_data); + DataType& z_perm_shift = std::get<46>(this->_data); + DataType& z_lookup_shift = std::get<47>(this->_data); + + std::vector get_wires() override { return { w_l, w_r, w_o, w_4 }; }; + std::vector get_ecc_op_wires() + { + return { ecc_op_wire_1, ecc_op_wire_2, ecc_op_wire_3, ecc_op_wire_4 }; + }; + // Gemini-specific getters. + std::vector get_unshifted() override + { + return { q_c, q_l, + q_r, q_o, + q_4, q_m, + q_arith, q_sort, + q_elliptic, q_aux, + q_lookup, sigma_1, + sigma_2, sigma_3, + sigma_4, id_1, + id_2, id_3, + id_4, table_1, + table_2, table_3, + table_4, lagrange_first, + lagrange_last, lagrange_ecc_op, + w_l, w_r, + w_o, w_4, + sorted_accum, z_perm, + z_lookup, ecc_op_wire_1, + ecc_op_wire_2, ecc_op_wire_3, + ecc_op_wire_4 }; + }; + std::vector get_to_be_shifted() override + { + return { table_1, table_2, table_3, table_4, w_l, w_r, w_o, w_4, sorted_accum, z_perm, z_lookup }; + }; + std::vector get_shifted() override + { + return { table_1_shift, table_2_shift, table_3_shift, table_4_shift, w_l_shift, w_r_shift, + w_o_shift, w_4_shift, sorted_accum_shift, z_perm_shift, z_lookup_shift }; + }; + + AllEntities() = default; + + AllEntities(const AllEntities& other) + : AllEntities_(other){}; + + AllEntities(AllEntities&& other) + : AllEntities_(other){}; + + AllEntities& operator=(const AllEntities& other) + { + if (this == &other) { + return *this; + } + AllEntities_::operator=(other); + return *this; + } + + AllEntities& operator=(AllEntities&& other) + { + AllEntities_::operator=(other); + return *this; + } + + ~AllEntities() = default; + }; + + public: + /** + * @brief The proving key is responsible for storing the polynomials used by the prover. + * @note TODO(Cody): Maybe multiple inheritance is the right thing here. In that case, nothing should eve inherit + * from ProvingKey. + */ + class ProvingKey : public ProvingKey_, + WitnessEntities> { + public: + // Expose constructors on the base class + using Base = ProvingKey_, + WitnessEntities>; + using Base::Base; + + std::vector memory_read_records; + std::vector memory_write_records; + + size_t num_ecc_op_gates; // needed to determine public input offset + + // The plookup wires that store plookup read data. + std::array get_table_column_wires() { return { w_l, w_r, w_o }; }; + }; + + /** + * @brief The verification key is responsible for storing the the commitments to the precomputed (non-witnessk) + * polynomials used by the verifier. + * + * @note Note the discrepancy with what sort of data is stored here vs in the proving key. We may want to resolve + * that, and split out separate PrecomputedPolynomials/Commitments data for clarity but also for portability of our + * circuits. + */ + using VerificationKey = VerificationKey_>; + + /** + * @brief A container for polynomials handles; only stores spans. + */ + using ProverPolynomials = AllEntities; + + /** + * @brief A container for storing the partially evaluated multivariates produced by sumcheck. + */ + class PartiallyEvaluatedMultivariates : public AllEntities { + + public: + PartiallyEvaluatedMultivariates() = default; + PartiallyEvaluatedMultivariates(const size_t circuit_size) + { + // Storage is only needed after the first partial evaluation, hence polynomials of size (n / 2) + for (auto& poly : this->_data) { + poly = Polynomial(circuit_size / 2); + } + } + }; + + /** + * @brief A container for univariates produced during the hot loop in sumcheck. + * @todo TODO(#390): Simplify this by moving MAX_RELATION_LENGTH? + */ + template + using ExtendedEdges = + AllEntities, sumcheck::Univariate>; + + /** + * @brief A container for the polynomials evaluations produced during sumcheck, which are purported to be the + * evaluations of polynomials committed in earlier rounds. + */ + class ClaimedEvaluations : public AllEntities { + public: + using Base = AllEntities; + using Base::Base; + ClaimedEvaluations(std::array _data_in) { this->_data = _data_in; } + }; + + /** + * @brief A container for commitment labels. + * @note It's debatable whether this should inherit from AllEntities. since most entries are not strictly needed. It + * has, however, been useful during debugging to have these labels available. + * + */ + class CommitmentLabels : public AllEntities { + public: + CommitmentLabels() + { + w_l = "W_L"; + w_r = "W_R"; + w_o = "W_O"; + w_4 = "W_4"; + z_perm = "Z_PERM"; + z_lookup = "Z_LOOKUP"; + sorted_accum = "SORTED_ACCUM"; + ecc_op_wire_1 = "ECC_OP_WIRE_1"; + ecc_op_wire_2 = "ECC_OP_WIRE_2"; + ecc_op_wire_3 = "ECC_OP_WIRE_3"; + ecc_op_wire_4 = "ECC_OP_WIRE_4"; + + // The ones beginning with "__" are only used for debugging + q_c = "__Q_C"; + q_l = "__Q_L"; + q_r = "__Q_R"; + q_o = "__Q_O"; + q_4 = "__Q_4"; + q_m = "__Q_M"; + q_arith = "__Q_ARITH"; + q_sort = "__Q_SORT"; + q_elliptic = "__Q_ELLIPTIC"; + q_aux = "__Q_AUX"; + q_lookup = "__Q_LOOKUP"; + sigma_1 = "__SIGMA_1"; + sigma_2 = "__SIGMA_2"; + sigma_3 = "__SIGMA_3"; + sigma_4 = "__SIGMA_4"; + id_1 = "__ID_1"; + id_2 = "__ID_2"; + id_3 = "__ID_3"; + id_4 = "__ID_4"; + table_1 = "__TABLE_1"; + table_2 = "__TABLE_2"; + table_3 = "__TABLE_3"; + table_4 = "__TABLE_4"; + lagrange_first = "__LAGRANGE_FIRST"; + lagrange_last = "__LAGRANGE_LAST"; + lagrange_ecc_op = "__Q_ECC_OP_QUEUE"; + }; + }; + + class VerifierCommitments : public AllEntities { + public: + VerifierCommitments(std::shared_ptr verification_key, VerifierTranscript transcript) + { + static_cast(transcript); + q_m = verification_key->q_m; + q_l = verification_key->q_l; + q_r = verification_key->q_r; + q_o = verification_key->q_o; + q_4 = verification_key->q_4; + q_c = verification_key->q_c; + q_arith = verification_key->q_arith; + q_sort = verification_key->q_sort; + q_elliptic = verification_key->q_elliptic; + q_aux = verification_key->q_aux; + q_lookup = verification_key->q_lookup; + sigma_1 = verification_key->sigma_1; + sigma_2 = verification_key->sigma_2; + sigma_3 = verification_key->sigma_3; + sigma_4 = verification_key->sigma_4; + id_1 = verification_key->id_1; + id_2 = verification_key->id_2; + id_3 = verification_key->id_3; + id_4 = verification_key->id_4; + table_1 = verification_key->table_1; + table_2 = verification_key->table_2; + table_3 = verification_key->table_3; + table_4 = verification_key->table_4; + lagrange_first = verification_key->lagrange_first; + lagrange_last = verification_key->lagrange_last; + lagrange_ecc_op = verification_key->lagrange_ecc_op; + } + }; +}; + +} // namespace proof_system::honk::flavor diff --git a/cpp/src/barretenberg/honk/proof_system/prover_library.cpp b/cpp/src/barretenberg/honk/proof_system/prover_library.cpp index a8163aad6..27da4a422 100644 --- a/cpp/src/barretenberg/honk/proof_system/prover_library.cpp +++ b/cpp/src/barretenberg/honk/proof_system/prover_library.cpp @@ -1,4 +1,5 @@ #include "prover_library.hpp" +#include "barretenberg/honk/flavor/goblin_ultra.hpp" #include "barretenberg/honk/flavor/standard.hpp" #include "barretenberg/honk/flavor/standard_grumpkin.hpp" #include "barretenberg/honk/flavor/ultra.hpp" @@ -86,12 +87,16 @@ void add_plookup_memory_records_to_wire_4(std::shared_ptr( std::shared_ptr& key, typename honk::flavor::Ultra::FF eta); template void add_plookup_memory_records_to_wire_4( std::shared_ptr& key, typename honk::flavor::Ultra::FF eta); +// flavor::UltraGrumpkin + template typename honk::flavor::UltraGrumpkin::Polynomial compute_sorted_list_accumulator( std::shared_ptr& key, typename honk::flavor::UltraGrumpkin::FF eta); @@ -100,4 +105,12 @@ template void add_plookup_memory_records_to_wire_4( std::shared_ptr& key, typename honk::flavor::UltraGrumpkin::FF eta); -} // namespace proof_system::honk::prover_library +// flavor::GoblinUltra + +template typename honk::flavor::GoblinUltra::Polynomial compute_sorted_list_accumulator( + std::shared_ptr& key, typename honk::flavor::GoblinUltra::FF eta); + +template void add_plookup_memory_records_to_wire_4( + std::shared_ptr& key, typename honk::flavor::GoblinUltra::FF eta); + +} // namespace proof_system::honk::prover_library \ No newline at end of file diff --git a/cpp/src/barretenberg/honk/proof_system/ultra_prover.cpp b/cpp/src/barretenberg/honk/proof_system/ultra_prover.cpp index 9e584a152..2022fac76 100644 --- a/cpp/src/barretenberg/honk/proof_system/ultra_prover.cpp +++ b/cpp/src/barretenberg/honk/proof_system/ultra_prover.cpp @@ -74,30 +74,28 @@ UltraProver_::UltraProver_(std::shared_ptr prover_polynomials.w_r_shift = key->w_r.shifted(); prover_polynomials.w_o_shift = key->w_o.shifted(); - // Add public inputs to transcript from the second wire polynomial; The PI have been written into the wires at the - // 0th or 1st index depending on whether or not a zero row is utilized. + if constexpr (IsGoblinFlavor) { + prover_polynomials.ecc_op_wire_1 = key->ecc_op_wire_1; + prover_polynomials.ecc_op_wire_2 = key->ecc_op_wire_2; + prover_polynomials.ecc_op_wire_3 = key->ecc_op_wire_3; + prover_polynomials.ecc_op_wire_4 = key->ecc_op_wire_4; + prover_polynomials.lagrange_ecc_op = key->lagrange_ecc_op; + } + + // Add public inputs to transcript from the second wire polynomial; This requires determination of the offset at + // which the PI have been written into the wires relative to the 0th index. std::span public_wires_source = prover_polynomials.w_r; - const size_t pub_input_offset = Flavor::has_zero_row ? 1 : 0; + pub_inputs_offset = Flavor::has_zero_row ? 1 : 0; + if constexpr (IsGoblinFlavor) { + pub_inputs_offset += key->num_ecc_op_gates; + } for (size_t i = 0; i < key->num_public_inputs; ++i) { - size_t idx = i + pub_input_offset; + size_t idx = i + pub_inputs_offset; public_inputs.emplace_back(public_wires_source[idx]); } } -/** - * @brief Commit to the first three wires only - * - */ -template void UltraProver_::compute_wire_commitments() -{ - auto wire_polys = key->get_wires(); - auto labels = commitment_labels.get_wires(); - for (size_t idx = 0; idx < 3; ++idx) { - queue.add_commitment(wire_polys[idx], labels[idx]); - } -} - /** * @brief Add circuit size, public input size, and public inputs to transcript * @@ -109,6 +107,7 @@ template void UltraProver_::execute_preamble_round( transcript.send_to_verifier("circuit_size", circuit_size); transcript.send_to_verifier("public_input_size", num_public_inputs); + transcript.send_to_verifier("pub_inputs_offset", static_cast(pub_inputs_offset)); for (size_t i = 0; i < key->num_public_inputs; ++i) { auto public_input_i = public_inputs[i]; @@ -122,11 +121,21 @@ template void UltraProver_::execute_preamble_round( */ template void UltraProver_::execute_wire_commitments_round() { + // Commit to the first three wire polynomials; the fourth is committed to after the addition of memory records. auto wire_polys = key->get_wires(); auto labels = commitment_labels.get_wires(); for (size_t idx = 0; idx < 3; ++idx) { queue.add_commitment(wire_polys[idx], labels[idx]); } + + // If Goblin, commit to the ECC op wire polynomials + if constexpr (IsGoblinFlavor) { + auto op_wire_polys = key->get_ecc_op_wires(); + auto labels = commitment_labels.get_ecc_op_wires(); + for (size_t idx = 0; idx < Flavor::NUM_WIRES; ++idx) { + queue.add_commitment(op_wire_polys[idx], labels[idx]); + } + } } /** @@ -162,7 +171,8 @@ template void UltraProver_::execute_grand_product_c // Compute and store parameters required by relations in Sumcheck auto [beta, gamma] = transcript.get_challenges("beta", "gamma"); - auto public_input_delta = compute_public_input_delta(public_inputs, beta, gamma, key->circuit_size); + auto public_input_delta = + compute_public_input_delta(public_inputs, beta, gamma, key->circuit_size, pub_inputs_offset); auto lookup_grand_product_delta = compute_lookup_grand_product_delta(beta, gamma, key->circuit_size); relation_parameters.beta = beta; @@ -338,5 +348,6 @@ template plonk::proof& UltraProver_::construct_proo template class UltraProver_; template class UltraProver_; +template class UltraProver_; } // namespace proof_system::honk diff --git a/cpp/src/barretenberg/honk/proof_system/ultra_prover.hpp b/cpp/src/barretenberg/honk/proof_system/ultra_prover.hpp index f848a9a67..953d41b91 100644 --- a/cpp/src/barretenberg/honk/proof_system/ultra_prover.hpp +++ b/cpp/src/barretenberg/honk/proof_system/ultra_prover.hpp @@ -1,4 +1,5 @@ #pragma once +#include "barretenberg/honk/flavor/goblin_ultra.hpp" #include "barretenberg/honk/flavor/ultra.hpp" #include "barretenberg/honk/flavor/ultra_grumpkin.hpp" #include "barretenberg/honk/pcs/gemini/gemini.hpp" @@ -38,14 +39,13 @@ template class UltraProver_ { void execute_shplonk_partial_evaluation_round(); void execute_final_pcs_round(); - void compute_wire_commitments(); - plonk::proof& export_proof(); plonk::proof& construct_proof(); ProverTranscript transcript; std::vector public_inputs; + size_t pub_inputs_offset; // offset of the PI relative to 0th index in the wire polynomials sumcheck::RelationParameters relation_parameters; @@ -80,6 +80,7 @@ template class UltraProver_ { extern template class UltraProver_; extern template class UltraProver_; +extern template class UltraProver_; using UltraProver = UltraProver_; diff --git a/cpp/src/barretenberg/honk/proof_system/ultra_verifier.cpp b/cpp/src/barretenberg/honk/proof_system/ultra_verifier.cpp index 0a149ac79..7fbdcd932 100644 --- a/cpp/src/barretenberg/honk/proof_system/ultra_verifier.cpp +++ b/cpp/src/barretenberg/honk/proof_system/ultra_verifier.cpp @@ -54,6 +54,7 @@ template bool UltraVerifier_::verify_proof(const plonk // TODO(Adrian): Change the initialization of the transcript to take the VK hash? const auto circuit_size = transcript.template receive_from_prover("circuit_size"); const auto public_input_size = transcript.template receive_from_prover("public_input_size"); + const auto pub_inputs_offset = transcript.template receive_from_prover("pub_inputs_offset"); if (circuit_size != key->circuit_size) { return false; @@ -68,11 +69,23 @@ template bool UltraVerifier_::verify_proof(const plonk public_inputs.emplace_back(public_input_i); } - // Get commitments to first three wires + // Get commitments to first three wire polynomials commitments.w_l = transcript.template receive_from_prover(commitment_labels.w_l); commitments.w_r = transcript.template receive_from_prover(commitment_labels.w_r); commitments.w_o = transcript.template receive_from_prover(commitment_labels.w_o); + // If Goblin, get commitments to ECC op wire polynomials + if constexpr (IsGoblinFlavor) { + commitments.ecc_op_wire_1 = + transcript.template receive_from_prover(commitment_labels.ecc_op_wire_1); + commitments.ecc_op_wire_2 = + transcript.template receive_from_prover(commitment_labels.ecc_op_wire_2); + commitments.ecc_op_wire_3 = + transcript.template receive_from_prover(commitment_labels.ecc_op_wire_3); + commitments.ecc_op_wire_4 = + transcript.template receive_from_prover(commitment_labels.ecc_op_wire_4); + } + // Get challenge for sorted list batching and wire four memory records auto eta = transcript.get_challenge("eta"); relation_parameters.eta = eta; @@ -84,7 +97,8 @@ template bool UltraVerifier_::verify_proof(const plonk // Get permutation challenges auto [beta, gamma] = transcript.get_challenges("beta", "gamma"); - const FF public_input_delta = compute_public_input_delta(public_inputs, beta, gamma, circuit_size); + const FF public_input_delta = + compute_public_input_delta(public_inputs, beta, gamma, circuit_size, pub_inputs_offset); const FF lookup_grand_product_delta = compute_lookup_grand_product_delta(beta, gamma, circuit_size); relation_parameters.beta = beta; @@ -159,5 +173,6 @@ template bool UltraVerifier_::verify_proof(const plonk template class UltraVerifier_; template class UltraVerifier_; +template class UltraVerifier_; } // namespace proof_system::honk diff --git a/cpp/src/barretenberg/honk/proof_system/ultra_verifier.hpp b/cpp/src/barretenberg/honk/proof_system/ultra_verifier.hpp index 39de3ebaf..9800497ba 100644 --- a/cpp/src/barretenberg/honk/proof_system/ultra_verifier.hpp +++ b/cpp/src/barretenberg/honk/proof_system/ultra_verifier.hpp @@ -1,4 +1,5 @@ #pragma once +#include "barretenberg/honk/flavor/goblin_ultra.hpp" #include "barretenberg/honk/flavor/ultra.hpp" #include "barretenberg/honk/flavor/ultra_grumpkin.hpp" #include "barretenberg/honk/sumcheck/sumcheck.hpp" @@ -29,6 +30,7 @@ template class UltraVerifier_ { extern template class UltraVerifier_; extern template class UltraVerifier_; +extern template class UltraVerifier_; using UltraVerifier = UltraVerifier_; diff --git a/cpp/src/barretenberg/honk/sumcheck/relations/ecc_op_queue_relation.hpp b/cpp/src/barretenberg/honk/sumcheck/relations/ecc_op_queue_relation.hpp new file mode 100644 index 000000000..20ce47058 --- /dev/null +++ b/cpp/src/barretenberg/honk/sumcheck/relations/ecc_op_queue_relation.hpp @@ -0,0 +1,116 @@ +#pragma once +#include +#include + +#include "../polynomials/univariate.hpp" +#include "relation_parameters.hpp" +#include "relation_types.hpp" + +namespace proof_system::honk::sumcheck { + +template class EccOpQueueRelationBase { + public: + // 1 + polynomial degree of this relation + static constexpr size_t RELATION_LENGTH = 3; // degree(q * (w - w_op_queue)) = 2 + + static constexpr size_t LEN_1 = 3; // wire - op-queue-wire consistency sub-relation 1 + static constexpr size_t LEN_2 = 3; // wire - op-queue-wire consistency sub-relation 2 + static constexpr size_t LEN_3 = 3; // wire - op-queue-wire consistency sub-relation 3 + static constexpr size_t LEN_4 = 3; // wire - op-queue-wire consistency sub-relation 4 + static constexpr size_t LEN_5 = 3; // op-queue-wire vanishes sub-relation 1 + static constexpr size_t LEN_6 = 3; // op-queue-wire vanishes sub-relation 2 + static constexpr size_t LEN_7 = 3; // op-queue-wire vanishes sub-relation 3 + static constexpr size_t LEN_8 = 3; // op-queue-wire vanishes sub-relation 4 + template