diff --git a/barretenberg/cpp/src/barretenberg/honk/composer/goblin/full_goblin_composer.test.cpp b/barretenberg/cpp/src/barretenberg/honk/composer/goblin/full_goblin_composer.test.cpp new file mode 100644 index 000000000000..e5d1995fb892 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/honk/composer/goblin/full_goblin_composer.test.cpp @@ -0,0 +1,197 @@ +#include +#include +#include + +#include "barretenberg/common/log.hpp" +#include "barretenberg/honk/composer/eccvm_composer.hpp" +#include "barretenberg/honk/composer/ultra_composer.hpp" +#include "barretenberg/honk/proof_system/ultra_prover.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" + +namespace test_full_goblin_composer { + +namespace { +auto& engine = numeric::random::get_debug_engine(); +} + +class FullGoblinComposerTests : 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 Point = Curve::AffineElement; + using CommitmentKey = proof_system::honk::pcs::CommitmentKey; + using GoblinUltraBuilder = proof_system::GoblinUltraCircuitBuilder; + using GoblinUltraComposer = proof_system::honk::GoblinUltraComposer; + using ECCVMFlavor = proof_system::honk::flavor::ECCVMGrumpkin; + using ECCVMBuilder = proof_system::ECCVMCircuitBuilder; + using ECCVMComposer = proof_system::honk::ECCVMComposer_; + using VMOp = proof_system_eccvm::VMOperation; + static constexpr size_t NUM_OP_QUEUE_COLUMNS = proof_system::honk::flavor::GoblinUltra::NUM_WIRES; + + /** + * @brief Generate a simple test circuit with some ECC op gates and conventional arithmetic gates + * + * @param builder + */ + void generate_test_circuit(auto& 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); + } + builder.queue_ecc_eq(); + + // 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. + * + * @param op_queue + */ + static void perform_op_queue_interactions_for_mock_first_circuit( + std::shared_ptr& op_queue) + { + auto builder = GoblinUltraBuilder(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 Test proof construction/verification for a circuit with ECC op gates, public inputs, and basic arithmetic + * gates + * @note We simulate op queue interactions with a previous circuit so the actual circuit under test utilizes an op queue + * with non-empty 'previous' data. This avoid complications with zero-commitments etc. + * + */ +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); + + // Construct a series of simple Goblin circuits; generate and verify their proofs + size_t NUM_CIRCUITS = 3; + for (size_t circuit_idx = 0; circuit_idx < NUM_CIRCUITS; ++circuit_idx) { + auto builder = GoblinUltraBuilder(op_queue); + + generate_test_circuit(builder); + + auto composer = GoblinUltraComposer(); + 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); + EXPECT_EQ(verified, true); + } + + // Construct an ECCVM circuit then generate and verify its proof + { + // Instantiate an ECCVM builder with the vm ops stored in the op queue + auto builder = ECCVMBuilder(op_queue->raw_ops); + + // // Can fiddle with one of the operands to trigger a failure + // builder.vm_operations[0].z1 *= 2; + + auto composer = ECCVMComposer(); + auto prover = composer.create_prover(builder); + auto proof = prover.construct_proof(); + auto verifier = composer.create_verifier(builder); + bool verified = verifier.verify_proof(proof); + ASSERT_TRUE(verified); + } +} + +/** + * @brief Check that ECCVM verification fails if ECC op queue operands are tampered with + * + */ +TEST_F(FullGoblinComposerTests, SimpleCircuitFailureCase) +{ + 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); + + // Construct a series of simple Goblin circuits; generate and verify their proofs + size_t NUM_CIRCUITS = 3; + for (size_t circuit_idx = 0; circuit_idx < NUM_CIRCUITS; ++circuit_idx) { + auto builder = GoblinUltraBuilder(op_queue); + + generate_test_circuit(builder); + + auto composer = GoblinUltraComposer(); + 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); + EXPECT_EQ(verified, true); + } + + // Construct an ECCVM circuit then generate and verify its proof + { + // Instantiate an ECCVM builder with the vm ops stored in the op queue + auto builder = ECCVMBuilder(op_queue->raw_ops); + + // Fiddle with one of the operands to trigger a failure + builder.vm_operations[0].z1 += 1; + + auto composer = ECCVMComposer(); + auto prover = composer.create_prover(builder); + auto proof = prover.construct_proof(); + auto verifier = composer.create_verifier(builder); + bool verified = verifier.verify_proof(proof); + EXPECT_EQ(verified, false); + } +} + +} // namespace test_full_goblin_composer diff --git a/barretenberg/cpp/src/barretenberg/honk/composer/goblin_ultra_composer.test.cpp b/barretenberg/cpp/src/barretenberg/honk/composer/goblin_ultra_composer.test.cpp index 07656ebd5912..5d33532eb1bd 100644 --- a/barretenberg/cpp/src/barretenberg/honk/composer/goblin_ultra_composer.test.cpp +++ b/barretenberg/cpp/src/barretenberg/honk/composer/goblin_ultra_composer.test.cpp @@ -5,6 +5,7 @@ #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/goblin_ultra_circuit_builder.hpp" #include "barretenberg/proof_system/circuit_builder/ultra_circuit_builder.hpp" using namespace proof_system::honk; @@ -18,51 +19,114 @@ auto& engine = numeric::random::get_debug_engine(); class GoblinUltraHonkComposerTests : public ::testing::Test { protected: static void SetUpTestSuite() { barretenberg::srs::init_crs_factory("../srs_db/ignition"); } + + using Curve = curve::BN254; + using FF = Curve::ScalarField; + using Point = Curve::AffineElement; + using CommitmentKey = pcs::CommitmentKey; + + /** + * @brief Generate a simple test circuit with some ECC op gates and conventional arithmetic gates + * + * @param builder + */ + void generate_test_circuit(auto& builder) + { + // Add some ecc op gates + for (size_t i = 0; i < 3; ++i) { + auto point = Point::one() * FF::random_element(); + auto scalar = FF::random_element(); + builder.queue_ecc_mul_accum(point, scalar); + } + builder.queue_ecc_eq(); + + // 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 Construct a goblin ultra circuit then generate a verify its proof + * + * @param op_queue + * @return auto + */ + bool construct_test_circuit_then_generate_and_verify_proof(auto& op_queue) + { + auto builder = proof_system::GoblinUltraCircuitBuilder(op_queue); + + generate_test_circuit(builder); + + auto composer = GoblinUltraComposer(); + 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 Test proof construction/verification for a circuit with ECC op gates, public inputs, and basic arithmetic * gates + * @note We simulate op queue interactions with a previous circuit so the actual circuit under test utilizes an op queue + * with non-empty 'previous' data. This avoid complications with zero-commitments etc. * */ -TEST_F(GoblinUltraHonkComposerTests, SimpleCircuit) +TEST_F(GoblinUltraHonkComposerTests, SingleCircuit) { - using fr = barretenberg::fr; - using g1 = barretenberg::g1; - auto builder = proof_system::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); - } + auto op_queue = std::make_shared(); - // 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) }); - } + // Add mock data to op queue to simulate interaction with a previous circuit + op_queue->populate_with_mock_initital_data(); + + // Construct a test circuit then generate and verify its proof + auto verified = construct_test_circuit_then_generate_and_verify_proof(op_queue); - auto composer = GoblinUltraComposer(); - 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); EXPECT_EQ(verified, true); } +/** + * @brief Test proof construction/verification for a circuit with ECC op gates, public inputs, and basic arithmetic + * gates + * + */ +TEST_F(GoblinUltraHonkComposerTests, MultipleCircuits) +{ + // Instantiate EccOpQueue. This will be shared across all circuits in the series + auto op_queue = std::make_shared(); + + // Add mock data to op queue to simulate interaction with a previous circuit + op_queue->populate_with_mock_initital_data(); + + // Construct multiple test circuits that share an ECC op queue. Generate and verify a proof for each. + size_t NUM_CIRCUITS = 3; + for (size_t i = 0; i < NUM_CIRCUITS; ++i) { + construct_test_circuit_then_generate_and_verify_proof(op_queue); + } + + // Compute the commitments to the aggregate op queue directly and check that they match those that were computed + // iteratively during transcript aggregation by the provers and stored in the op queue. + size_t aggregate_op_queue_size = op_queue->current_ultra_ops_size; + auto crs_factory = std::make_shared>("../srs_db/ignition"); + auto commitment_key = std::make_shared(aggregate_op_queue_size, crs_factory); + size_t idx = 0; + for (auto& result : op_queue->ultra_ops_commitments) { + auto expected = commitment_key->commit(op_queue->ultra_ops[idx++]); + EXPECT_EQ(result, expected); + } +} + } // namespace test_ultra_honk_composer diff --git a/barretenberg/cpp/src/barretenberg/honk/flavor/ecc_vm.hpp b/barretenberg/cpp/src/barretenberg/honk/flavor/ecc_vm.hpp index c03b1e5dded7..7cceb23abbab 100644 --- a/barretenberg/cpp/src/barretenberg/honk/flavor/ecc_vm.hpp +++ b/barretenberg/cpp/src/barretenberg/honk/flavor/ecc_vm.hpp @@ -816,7 +816,7 @@ template class ECCVMBa }; }; -class ECCVM : public ECCVMBase> {}; +class ECCVM : public ECCVMBase> {}; class ECCVMGrumpkin : public ECCVMBase> {}; // NOLINTEND(cppcoreguidelines-avoid-const-or-ref-data-members) diff --git a/barretenberg/cpp/src/barretenberg/honk/flavor/goblin_ultra.hpp b/barretenberg/cpp/src/barretenberg/honk/flavor/goblin_ultra.hpp index ae5411f7e328..006bba75d6e5 100644 --- a/barretenberg/cpp/src/barretenberg/honk/flavor/goblin_ultra.hpp +++ b/barretenberg/cpp/src/barretenberg/honk/flavor/goblin_ultra.hpp @@ -2,7 +2,7 @@ #include "barretenberg/honk/pcs/kzg/kzg.hpp" #include "barretenberg/honk/transcript/transcript.hpp" #include "barretenberg/polynomials/univariate.hpp" -#include "barretenberg/proof_system/circuit_builder/ultra_circuit_builder.hpp" +#include "barretenberg/proof_system/circuit_builder/goblin_ultra_circuit_builder.hpp" #include "barretenberg/proof_system/flavor/flavor.hpp" #include "barretenberg/proof_system/relations/auxiliary_relation.hpp" #include "barretenberg/proof_system/relations/ecc_op_queue_relation.hpp" @@ -16,13 +16,13 @@ namespace proof_system::honk::flavor { class GoblinUltra { public: - using CircuitBuilder = UltraCircuitBuilder; + using CircuitBuilder = GoblinUltraCircuitBuilder; using Curve = curve::BN254; - using PCS = pcs::kzg::KZG; + using FF = Curve::ScalarField; using GroupElement = Curve::Element; using Commitment = Curve::AffineElement; using CommitmentHandle = Curve::AffineElement; - using FF = Curve::ScalarField; + using PCS = pcs::kzg::KZG; using Polynomial = barretenberg::Polynomial; using PolynomialHandle = std::span; using CommitmentKey = pcs::CommitmentKey; @@ -288,6 +288,8 @@ class GoblinUltra { size_t num_ecc_op_gates; // needed to determine public input offset + std::shared_ptr op_queue; + // The plookup wires that store plookup read data. std::array get_table_column_wires() { return { w_l, w_r, w_o }; }; }; diff --git a/barretenberg/cpp/src/barretenberg/honk/flavor/goblin_ultra_recursive.hpp b/barretenberg/cpp/src/barretenberg/honk/flavor/goblin_ultra_recursive.hpp new file mode 100644 index 000000000000..12f96ff105cf --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/honk/flavor/goblin_ultra_recursive.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/polynomials/barycentric.hpp" +#include "barretenberg/polynomials/univariate.hpp" + +#include "barretenberg/honk/flavor/goblin_ultra.hpp" +#include "barretenberg/honk/transcript/transcript.hpp" +#include "barretenberg/polynomials/evaluation_domain.hpp" +#include "barretenberg/polynomials/polynomial.hpp" +#include "barretenberg/proof_system/circuit_builder/goblin_ultra_circuit_builder.hpp" +#include "barretenberg/proof_system/flavor/flavor.hpp" +#include "barretenberg/proof_system/relations/auxiliary_relation.hpp" +#include "barretenberg/proof_system/relations/ecc_op_queue_relation.hpp" +#include "barretenberg/proof_system/relations/elliptic_relation.hpp" +#include "barretenberg/proof_system/relations/gen_perm_sort_relation.hpp" +#include "barretenberg/proof_system/relations/lookup_relation.hpp" +#include "barretenberg/proof_system/relations/permutation_relation.hpp" +#include "barretenberg/proof_system/relations/ultra_arithmetic_relation.hpp" +#include "barretenberg/srs/factories/crs_factory.hpp" +#include +#include +#include +#include +#include +#include + +#include "barretenberg/stdlib/primitives/curves/bn254.hpp" +#include "barretenberg/stdlib/primitives/field/field.hpp" + +namespace proof_system::honk::flavor { + +/** + * @brief The recursive counterpart to the "native" Goblin Ultra flavor. + * @details This flavor can be used to instantiate a recursive Ultra Honk verifier for a proof created using the + * GoblinUltra flavor. It is similar in structure to its native counterpart with two main differences: 1) the + * curve types are stdlib types (e.g. field_t instead of field) and 2) it does not specify any Prover related types + * (e.g. Polynomial, ExtendedEdges, etc.) since we do not emulate prover computation in circuits, i.e. it only makes + * sense to instantiate a Verifier with this flavor. + * + * @note Unlike conventional flavors, "recursive" flavors are templated by a builder (much like native vs stdlib types). + * This is because the flavor itself determines the details of the underlying verifier algorithm (i.e. the set of + * relations), while the Builder determines the arithmetization of that algorithm into a circuit. + * + * @tparam BuilderType Determines the arithmetization of the verifier circuit defined based on this flavor. + */ +template class GoblinUltraRecursive_ { + public: + using CircuitBuilder = BuilderType; // Determines arithmetization of circuit instantiated with this flavor + using Curve = plonk::stdlib::bn254; + using GroupElement = typename Curve::Element; + using Commitment = typename Curve::Element; + using CommitmentHandle = typename Curve::Element; + using FF = typename Curve::ScalarField; + + // Note(luke): Eventually this may not be needed at all + using VerifierCommitmentKey = pcs::VerifierCommitmentKey; + + static constexpr size_t NUM_WIRES = flavor::GoblinUltra::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 + + // define the tuple of Relations that comprise the Sumcheck relation + using Relations = std::tuple, + proof_system::UltraPermutationRelation, + proof_system::LookupRelation, + proof_system::GenPermSortRelation, + proof_system::EllipticRelation, + proof_system::AuxiliaryRelation, + proof_system::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()); + + 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 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. + */ + class VerificationKey : public VerificationKey_> { + public: + /** + * @brief Construct a new Verification Key with stdlib types from a provided native verification key + * + * @param builder + * @param native_key Native verification key from which to extract the precomputed commitments + */ + VerificationKey(CircuitBuilder* builder, auto native_key) + : VerificationKey_>(native_key->circuit_size, + native_key->num_public_inputs) + { + this->q_m = Commitment::from_witness(builder, native_key->q_m); + this->q_l = Commitment::from_witness(builder, native_key->q_l); + this->q_r = Commitment::from_witness(builder, native_key->q_r); + this->q_o = Commitment::from_witness(builder, native_key->q_o); + this->q_4 = Commitment::from_witness(builder, native_key->q_4); + this->q_c = Commitment::from_witness(builder, native_key->q_c); + this->q_arith = Commitment::from_witness(builder, native_key->q_arith); + this->q_sort = Commitment::from_witness(builder, native_key->q_sort); + this->q_elliptic = Commitment::from_witness(builder, native_key->q_elliptic); + this->q_aux = Commitment::from_witness(builder, native_key->q_aux); + this->q_lookup = Commitment::from_witness(builder, native_key->q_lookup); + this->sigma_1 = Commitment::from_witness(builder, native_key->sigma_1); + this->sigma_2 = Commitment::from_witness(builder, native_key->sigma_2); + this->sigma_3 = Commitment::from_witness(builder, native_key->sigma_3); + this->sigma_4 = Commitment::from_witness(builder, native_key->sigma_4); + this->id_1 = Commitment::from_witness(builder, native_key->id_1); + this->id_2 = Commitment::from_witness(builder, native_key->id_2); + this->id_3 = Commitment::from_witness(builder, native_key->id_3); + this->id_4 = Commitment::from_witness(builder, native_key->id_4); + this->table_1 = Commitment::from_witness(builder, native_key->table_1); + this->table_2 = Commitment::from_witness(builder, native_key->table_2); + this->table_3 = Commitment::from_witness(builder, native_key->table_3); + this->table_4 = Commitment::from_witness(builder, native_key->table_4); + this->lagrange_first = Commitment::from_witness(builder, native_key->lagrange_first); + this->lagrange_last = Commitment::from_witness(builder, native_key->lagrange_last); + this->lagrange_ecc_op = Commitment::from_witness(builder, native_key->lagrange_ecc_op); + }; + }; + + /** + * @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() + { + this->w_l = "W_L"; + this->w_r = "W_R"; + this->w_o = "W_O"; + this->w_4 = "W_4"; + this->z_perm = "Z_PERM"; + this->z_lookup = "Z_LOOKUP"; + this->sorted_accum = "SORTED_ACCUM"; + this->ecc_op_wire_1 = "ECC_OP_WIRE_1"; + this->ecc_op_wire_2 = "ECC_OP_WIRE_2"; + this->ecc_op_wire_3 = "ECC_OP_WIRE_3"; + this->ecc_op_wire_4 = "ECC_OP_WIRE_4"; + + // The ones beginning with "__" are only used for debugging + this->q_c = "__Q_C"; + this->q_l = "__Q_L"; + this->q_r = "__Q_R"; + this->q_o = "__Q_O"; + this->q_4 = "__Q_4"; + this->q_m = "__Q_M"; + this->q_arith = "__Q_ARITH"; + this->q_sort = "__Q_SORT"; + this->q_elliptic = "__Q_ELLIPTIC"; + this->q_aux = "__Q_AUX"; + this->q_lookup = "__Q_LOOKUP"; + this->sigma_1 = "__SIGMA_1"; + this->sigma_2 = "__SIGMA_2"; + this->sigma_3 = "__SIGMA_3"; + this->sigma_4 = "__SIGMA_4"; + this->id_1 = "__ID_1"; + this->id_2 = "__ID_2"; + this->id_3 = "__ID_3"; + this->id_4 = "__ID_4"; + this->table_1 = "__TABLE_1"; + this->table_2 = "__TABLE_2"; + this->table_3 = "__TABLE_3"; + this->table_4 = "__TABLE_4"; + this->lagrange_first = "__LAGRANGE_FIRST"; + this->lagrange_last = "__LAGRANGE_LAST"; + this->lagrange_ecc_op = "__Q_ECC_OP_QUEUE"; + }; + }; + + class VerifierCommitments : public AllEntities { + public: + VerifierCommitments(std::shared_ptr verification_key) + { + this->q_m = verification_key->q_m; + this->q_l = verification_key->q_l; + this->q_r = verification_key->q_r; + this->q_o = verification_key->q_o; + this->q_4 = verification_key->q_4; + this->q_c = verification_key->q_c; + this->q_arith = verification_key->q_arith; + this->q_sort = verification_key->q_sort; + this->q_elliptic = verification_key->q_elliptic; + this->q_aux = verification_key->q_aux; + this->q_lookup = verification_key->q_lookup; + this->sigma_1 = verification_key->sigma_1; + this->sigma_2 = verification_key->sigma_2; + this->sigma_3 = verification_key->sigma_3; + this->sigma_4 = verification_key->sigma_4; + this->id_1 = verification_key->id_1; + this->id_2 = verification_key->id_2; + this->id_3 = verification_key->id_3; + this->id_4 = verification_key->id_4; + this->table_1 = verification_key->table_1; + this->table_2 = verification_key->table_2; + this->table_3 = verification_key->table_3; + this->table_4 = verification_key->table_4; + this->lagrange_first = verification_key->lagrange_first; + this->lagrange_last = verification_key->lagrange_last; + this->lagrange_ecc_op = verification_key->lagrange_ecc_op; + } + }; +}; + +} // namespace proof_system::honk::flavor diff --git a/barretenberg/cpp/src/barretenberg/honk/flavor/ultra_recursive.hpp b/barretenberg/cpp/src/barretenberg/honk/flavor/ultra_recursive.hpp index 9d98f4a1c256..5168265cc089 100644 --- a/barretenberg/cpp/src/barretenberg/honk/flavor/ultra_recursive.hpp +++ b/barretenberg/cpp/src/barretenberg/honk/flavor/ultra_recursive.hpp @@ -5,6 +5,7 @@ #include "barretenberg/polynomials/barycentric.hpp" #include "barretenberg/polynomials/univariate.hpp" +#include "barretenberg/honk/flavor/ultra.hpp" #include "barretenberg/honk/transcript/transcript.hpp" #include "barretenberg/polynomials/evaluation_domain.hpp" #include "barretenberg/polynomials/polynomial.hpp" @@ -17,6 +18,7 @@ #include "barretenberg/proof_system/relations/permutation_relation.hpp" #include "barretenberg/proof_system/relations/ultra_arithmetic_relation.hpp" #include "barretenberg/srs/factories/crs_factory.hpp" + #include #include #include @@ -37,20 +39,25 @@ namespace proof_system::honk::flavor { * (e.g. Polynomial, ExtendedEdges, etc.) since we do not emulate prover computation in circuits, i.e. it only makes * sense to instantiate a Verifier with this flavor. * + * @note Unlike conventional flavors, "recursive" flavors are templated by a builder (much like native vs stdlib types). + * This is because the flavor itself determines the details of the underlying verifier algorithm (i.e. the set of + * relations), while the Builder determines the arithmetization of that algorithm into a circuit. + * + * @tparam BuilderType Determines the arithmetization of the verifier circuit defined based on this flavor. */ -class UltraRecursive { +template class UltraRecursive_ { public: - using CircuitBuilder = UltraCircuitBuilder; + using CircuitBuilder = BuilderType; // Determines arithmetization of circuit instantiated with this flavor using Curve = plonk::stdlib::bn254; - using GroupElement = Curve::Element; - using Commitment = Curve::Element; - using CommitmentHandle = Curve::Element; - using FF = Curve::ScalarField; + using GroupElement = typename Curve::Element; + using Commitment = typename Curve::Element; + using CommitmentHandle = typename Curve::Element; + using FF = typename Curve::ScalarField; // Note(luke): Eventually this may not be needed at all using VerifierCommitmentKey = pcs::VerifierCommitmentKey; - static constexpr size_t NUM_WIRES = CircuitBuilder::NUM_WIRES; + static constexpr size_t NUM_WIRES = flavor::Ultra::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. @@ -114,8 +121,6 @@ class UltraRecursive { DataType& lagrange_first = std::get<23>(this->_data); DataType& lagrange_last = std::get<24>(this->_data); - 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 }; @@ -274,31 +279,31 @@ class UltraRecursive { : VerificationKey_>(native_key->circuit_size, native_key->num_public_inputs) { - q_m = Commitment::from_witness(builder, native_key->q_m); - q_l = Commitment::from_witness(builder, native_key->q_l); - q_r = Commitment::from_witness(builder, native_key->q_r); - q_o = Commitment::from_witness(builder, native_key->q_o); - q_4 = Commitment::from_witness(builder, native_key->q_4); - q_c = Commitment::from_witness(builder, native_key->q_c); - q_arith = Commitment::from_witness(builder, native_key->q_arith); - q_sort = Commitment::from_witness(builder, native_key->q_sort); - q_elliptic = Commitment::from_witness(builder, native_key->q_elliptic); - q_aux = Commitment::from_witness(builder, native_key->q_aux); - q_lookup = Commitment::from_witness(builder, native_key->q_lookup); - sigma_1 = Commitment::from_witness(builder, native_key->sigma_1); - sigma_2 = Commitment::from_witness(builder, native_key->sigma_2); - sigma_3 = Commitment::from_witness(builder, native_key->sigma_3); - sigma_4 = Commitment::from_witness(builder, native_key->sigma_4); - id_1 = Commitment::from_witness(builder, native_key->id_1); - id_2 = Commitment::from_witness(builder, native_key->id_2); - id_3 = Commitment::from_witness(builder, native_key->id_3); - id_4 = Commitment::from_witness(builder, native_key->id_4); - table_1 = Commitment::from_witness(builder, native_key->table_1); - table_2 = Commitment::from_witness(builder, native_key->table_2); - table_3 = Commitment::from_witness(builder, native_key->table_3); - table_4 = Commitment::from_witness(builder, native_key->table_4); - lagrange_first = Commitment::from_witness(builder, native_key->lagrange_first); - lagrange_last = Commitment::from_witness(builder, native_key->lagrange_last); + this->q_m = Commitment::from_witness(builder, native_key->q_m); + this->q_l = Commitment::from_witness(builder, native_key->q_l); + this->q_r = Commitment::from_witness(builder, native_key->q_r); + this->q_o = Commitment::from_witness(builder, native_key->q_o); + this->q_4 = Commitment::from_witness(builder, native_key->q_4); + this->q_c = Commitment::from_witness(builder, native_key->q_c); + this->q_arith = Commitment::from_witness(builder, native_key->q_arith); + this->q_sort = Commitment::from_witness(builder, native_key->q_sort); + this->q_elliptic = Commitment::from_witness(builder, native_key->q_elliptic); + this->q_aux = Commitment::from_witness(builder, native_key->q_aux); + this->q_lookup = Commitment::from_witness(builder, native_key->q_lookup); + this->sigma_1 = Commitment::from_witness(builder, native_key->sigma_1); + this->sigma_2 = Commitment::from_witness(builder, native_key->sigma_2); + this->sigma_3 = Commitment::from_witness(builder, native_key->sigma_3); + this->sigma_4 = Commitment::from_witness(builder, native_key->sigma_4); + this->id_1 = Commitment::from_witness(builder, native_key->id_1); + this->id_2 = Commitment::from_witness(builder, native_key->id_2); + this->id_3 = Commitment::from_witness(builder, native_key->id_3); + this->id_4 = Commitment::from_witness(builder, native_key->id_4); + this->table_1 = Commitment::from_witness(builder, native_key->table_1); + this->table_2 = Commitment::from_witness(builder, native_key->table_2); + this->table_3 = Commitment::from_witness(builder, native_key->table_3); + this->table_4 = Commitment::from_witness(builder, native_key->table_4); + this->lagrange_first = Commitment::from_witness(builder, native_key->lagrange_first); + this->lagrange_last = Commitment::from_witness(builder, native_key->lagrange_last); }; }; @@ -323,40 +328,40 @@ class UltraRecursive { 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"; + this->w_l = "W_L"; + this->w_r = "W_R"; + this->w_o = "W_O"; + this->w_4 = "W_4"; + this->z_perm = "Z_PERM"; + this->z_lookup = "Z_LOOKUP"; + this->sorted_accum = "SORTED_ACCUM"; // 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"; + this->q_c = "__Q_C"; + this->q_l = "__Q_L"; + this->q_r = "__Q_R"; + this->q_o = "__Q_O"; + this->q_4 = "__Q_4"; + this->q_m = "__Q_M"; + this->q_arith = "__Q_ARITH"; + this->q_sort = "__Q_SORT"; + this->q_elliptic = "__Q_ELLIPTIC"; + this->q_aux = "__Q_AUX"; + this->q_lookup = "__Q_LOOKUP"; + this->sigma_1 = "__SIGMA_1"; + this->sigma_2 = "__SIGMA_2"; + this->sigma_3 = "__SIGMA_3"; + this->sigma_4 = "__SIGMA_4"; + this->id_1 = "__ID_1"; + this->id_2 = "__ID_2"; + this->id_3 = "__ID_3"; + this->id_4 = "__ID_4"; + this->table_1 = "__TABLE_1"; + this->table_2 = "__TABLE_2"; + this->table_3 = "__TABLE_3"; + this->table_4 = "__TABLE_4"; + this->lagrange_first = "__LAGRANGE_FIRST"; + this->lagrange_last = "__LAGRANGE_LAST"; }; }; @@ -364,31 +369,31 @@ class UltraRecursive { public: VerifierCommitments(std::shared_ptr verification_key) { - 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; + this->q_m = verification_key->q_m; + this->q_l = verification_key->q_l; + this->q_r = verification_key->q_r; + this->q_o = verification_key->q_o; + this->q_4 = verification_key->q_4; + this->q_c = verification_key->q_c; + this->q_arith = verification_key->q_arith; + this->q_sort = verification_key->q_sort; + this->q_elliptic = verification_key->q_elliptic; + this->q_aux = verification_key->q_aux; + this->q_lookup = verification_key->q_lookup; + this->sigma_1 = verification_key->sigma_1; + this->sigma_2 = verification_key->sigma_2; + this->sigma_3 = verification_key->sigma_3; + this->sigma_4 = verification_key->sigma_4; + this->id_1 = verification_key->id_1; + this->id_2 = verification_key->id_2; + this->id_3 = verification_key->id_3; + this->id_4 = verification_key->id_4; + this->table_1 = verification_key->table_1; + this->table_2 = verification_key->table_2; + this->table_3 = verification_key->table_3; + this->table_4 = verification_key->table_4; + this->lagrange_first = verification_key->lagrange_first; + this->lagrange_last = verification_key->lagrange_last; } }; }; diff --git a/barretenberg/cpp/src/barretenberg/honk/instance/prover_instance.cpp b/barretenberg/cpp/src/barretenberg/honk/instance/prover_instance.cpp index 890a006ce55f..284f1616b681 100644 --- a/barretenberg/cpp/src/barretenberg/honk/instance/prover_instance.cpp +++ b/barretenberg/cpp/src/barretenberg/honk/instance/prover_instance.cpp @@ -23,7 +23,10 @@ void ProverInstance_::compute_circuit_size_parameters(Circuit& circuit) // Get num conventional gates, num public inputs and num Goblin style ECC op gates const size_t num_gates = circuit.num_gates; num_public_inputs = circuit.public_inputs.size(); - num_ecc_op_gates = circuit.num_ecc_op_gates; + num_ecc_op_gates = 0; + if constexpr (IsGoblinFlavor) { + num_ecc_op_gates = circuit.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 + num_zero_rows; @@ -260,6 +263,7 @@ std::shared_ptr ProverInstance_::compute_pr if constexpr (IsGoblinFlavor) { proving_key->num_ecc_op_gates = num_ecc_op_gates; + proving_key->op_queue = circuit.op_queue; } } diff --git a/barretenberg/cpp/src/barretenberg/honk/instance/prover_instance.test.cpp b/barretenberg/cpp/src/barretenberg/honk/instance/prover_instance.test.cpp index 1cced414dd23..593a166b885f 100644 --- a/barretenberg/cpp/src/barretenberg/honk/instance/prover_instance.test.cpp +++ b/barretenberg/cpp/src/barretenberg/honk/instance/prover_instance.test.cpp @@ -12,6 +12,7 @@ namespace instance_tests { template class InstanceTests : public testing::Test { using FF = typename Flavor::FF; using Polynomial = barretenberg::Polynomial; + using Builder = typename Flavor::CircuitBuilder; public: /** @@ -47,7 +48,7 @@ template class InstanceTests : public testing::Test { static void test_sorted_list_accumulator_construction() { // Construct a simple circuit of size n = 8 (i.e. the minimum circuit size) - auto builder = proof_system::UltraCircuitBuilder(); + Builder builder; auto a = 2; builder.add_variable(a); diff --git a/barretenberg/cpp/src/barretenberg/honk/pcs/gemini/gemini.hpp b/barretenberg/cpp/src/barretenberg/honk/pcs/gemini/gemini.hpp index e24f89b76daa..526c7bd84d2a 100644 --- a/barretenberg/cpp/src/barretenberg/honk/pcs/gemini/gemini.hpp +++ b/barretenberg/cpp/src/barretenberg/honk/pcs/gemini/gemini.hpp @@ -111,7 +111,7 @@ template class GeminiProver_ { const Fr& r_challenge); }; // namespace proof_system::honk::pcs::gemini -template class GeminiVerifier_ { +template class GeminiVerifier_ { using Fr = typename Curve::ScalarField; using GroupElement = typename Curve::Element; using Commitment = typename Curve::AffineElement; @@ -247,8 +247,8 @@ template class GeminiVerifier_ { // TODO(#707): these batch muls include the use of 1 as a scalar. This is handled appropriately as a non-mul // (add-accumulate) in the goblin batch_mul but is done inefficiently as a scalar mul in the conventional // emulated batch mul. - C0_r_pos = GroupElement::template batch_mul(commitments, { one, r_inv }); - C0_r_neg = GroupElement::template batch_mul(commitments, { one, -r_inv }); + C0_r_pos = GroupElement::batch_mul(commitments, { one, r_inv }); + C0_r_neg = GroupElement::batch_mul(commitments, { one, -r_inv }); } else { C0_r_pos = batched_f; C0_r_neg = batched_f; diff --git a/barretenberg/cpp/src/barretenberg/honk/pcs/kzg/kzg.hpp b/barretenberg/cpp/src/barretenberg/honk/pcs/kzg/kzg.hpp index ab2c8d8bb7a2..6bd41ac7befb 100644 --- a/barretenberg/cpp/src/barretenberg/honk/pcs/kzg/kzg.hpp +++ b/barretenberg/cpp/src/barretenberg/honk/pcs/kzg/kzg.hpp @@ -11,7 +11,7 @@ namespace proof_system::honk::pcs::kzg { -template class KZG { +template class KZG { using CK = CommitmentKey; using VK = VerifierCommitmentKey; using Fr = typename Curve::ScalarField; @@ -88,7 +88,7 @@ template class KZG { auto one = Fr(builder, 1); std::vector commitments = { claim.commitment, quotient_commitment }; std::vector scalars = { one, claim.opening_pair.challenge }; - P_0 = GroupElement::template batch_mul(commitments, scalars); + P_0 = GroupElement::batch_mul(commitments, scalars); // Note: This implementation assumes the evaluation is zero (as is the case for shplonk). ASSERT(claim.opening_pair.evaluation.get_value() == 0); } else { diff --git a/barretenberg/cpp/src/barretenberg/honk/pcs/shplonk/shplonk.hpp b/barretenberg/cpp/src/barretenberg/honk/pcs/shplonk/shplonk.hpp index 17c452471dcf..784641012e1b 100644 --- a/barretenberg/cpp/src/barretenberg/honk/pcs/shplonk/shplonk.hpp +++ b/barretenberg/cpp/src/barretenberg/honk/pcs/shplonk/shplonk.hpp @@ -145,7 +145,7 @@ template class ShplonkProver_ { * @brief Shplonk Verifier * */ -template class ShplonkVerifier_ { +template class ShplonkVerifier_ { using Fr = typename Curve::ScalarField; using GroupElement = typename Curve::Element; using Commitment = typename Curve::AffineElement; @@ -231,7 +231,7 @@ template class ShplonkVerifier_ { scalars.emplace_back(G_commitment_constant); // [G] += G₀⋅[1] = [G] + (∑ⱼ ρʲ ⋅ vⱼ / ( r − xⱼ ))⋅[1] - G_commitment = GroupElement::template batch_mul(commitments, scalars); + G_commitment = GroupElement::batch_mul(commitments, scalars); } else { // [G] = [Q] - ∑ⱼ ρʲ / ( r − xⱼ )⋅[fⱼ] + G₀⋅[1] diff --git a/barretenberg/cpp/src/barretenberg/honk/proof_system/ultra_prover.cpp b/barretenberg/cpp/src/barretenberg/honk/proof_system/ultra_prover.cpp index f700ea55ab6c..393e896059ca 100644 --- a/barretenberg/cpp/src/barretenberg/honk/proof_system/ultra_prover.cpp +++ b/barretenberg/cpp/src/barretenberg/honk/proof_system/ultra_prover.cpp @@ -156,16 +156,119 @@ template void UltraProver_::execute_univariatizatio template void UltraProver_::execute_pcs_evaluation_round() { const FF r_challenge = transcript.get_challenge("Gemini:r"); - gemini_output = Gemini::compute_fold_polynomial_evaluations( + univariate_openings = Gemini::compute_fold_polynomial_evaluations( sumcheck_output.challenge_point, std::move(gemini_polynomials), r_challenge); for (size_t l = 0; l < instance->proving_key->log_circuit_size; ++l) { std::string label = "Gemini:a_" + std::to_string(l); - const auto& evaluation = gemini_output.opening_pairs[l + 1].evaluation; + const auto& evaluation = univariate_openings.opening_pairs[l + 1].evaluation; transcript.send_to_verifier(label, evaluation); } } +/** + * @brief Prove proper construction of the aggregate Goblin ECC op queue polynomials T_i^(j), j = 1,2,3,4. + * @details Let T_i^(j) be the jth column of the aggregate op queue after incorporating the contribution from the + * present circuit. T_{i-1}^(j) corresponds to the aggregate op queue at the previous stage and $t_i^(j)$ represents + * the contribution from the present circuit only. For each j, we have the relationship T_i = T_{i-1} + right_shift(t_i, + * M_{i-1}), where the shift magnitude M_{i-1} is the length of T_{i-1}. This stage of the protocol demonstrates that + * the aggregate op queue has been constructed correctly. + * + */ +template void UltraProver_::execute_op_queue_transcript_aggregation_round() +{ + if constexpr (IsGoblinFlavor) { + // Extract size M_{i-1} of T_{i-1} from op_queue + size_t prev_op_queue_size = instance->proving_key->op_queue->get_previous_size(); // M_{i-1} + // TODO(#723): Cannot currently support an empty T_{i-1}. Need to be able to properly handle zero commitment. + ASSERT(prev_op_queue_size > 0); + + auto circuit_size = instance->proving_key->circuit_size; + + // TODO(#723): The below assert ensures that M_{i-1} + m_i < n, i.e. the right shifted result can be expressed + // as a size n polynomial. If this is not the case then we should still be able to proceed without increasing + // the circuit size but need to handle with care. + ASSERT(prev_op_queue_size + instance->proving_key->num_ecc_op_gates < circuit_size); // M_{i-1} + m_i < n + + // Construct right-shift of op wires t_i^{shift} so that T_i(X) = T_{i-1}(X) + t_i^{shift}(X). + // Note: The op_wire polynomials (like all others) have constant coefficient equal to zero. Thus to obtain + // t_i^{shift} we must left-shift by 1 then right-shift by M_{i-1}, or equivalently, right-shift by + // M_{i-1} - 1. + std::array right_shifted_op_wires; + auto op_wires = instance->proving_key->get_ecc_op_wires(); + for (size_t i = 0; i < op_wires.size(); ++i) { + // Right shift by M_{i-1} - 1. + right_shifted_op_wires[i].set_to_right_shifted(op_wires[i], prev_op_queue_size - 1); + } + + // Compute/get commitments [t_i^{shift}], [T_{i-1}], and [T_i] and add to transcript + std::array prev_aggregate_op_queue_commitments; + std::array shifted_op_wire_commitments; + std::array aggregate_op_queue_commitments; + for (size_t idx = 0; idx < right_shifted_op_wires.size(); ++idx) { + // Get previous transcript commitment [T_{i-1}] from op queue + prev_aggregate_op_queue_commitments[idx] = instance->proving_key->op_queue->ultra_ops_commitments[idx]; + // Compute commitment [t_i^{shift}] directly + shifted_op_wire_commitments[idx] = pcs_commitment_key->commit(right_shifted_op_wires[idx]); + // Compute updated aggregate transcript commitmen as [T_i] = [T_{i-1}] + [t_i^{shift}] + aggregate_op_queue_commitments[idx] = + prev_aggregate_op_queue_commitments[idx] + shifted_op_wire_commitments[idx]; + + std::string suffix = std::to_string(idx + 1); + transcript.send_to_verifier("PREV_AGG_OP_QUEUE_" + suffix, prev_aggregate_op_queue_commitments[idx]); + transcript.send_to_verifier("SHIFTED_OP_WIRE_" + suffix, shifted_op_wire_commitments[idx]); + transcript.send_to_verifier("AGG_OP_QUEUE_" + suffix, aggregate_op_queue_commitments[idx]); + } + + // Store the commitments [T_{i}] (to be used later in subsequent iterations as [T_{i-1}]). + instance->proving_key->op_queue->set_commitment_data(aggregate_op_queue_commitments); + + // Compute evaluations T_i(\kappa), T_{i-1}(\kappa), t_i^{shift}(\kappa), add to transcript. For each polynomial + // we add a univariate opening claim {(\kappa, p(\kappa)), p(X)} to the set of claims to be combined in the + // batch univariate polynomial Q in Shplonk. (The other univariate claims come from the output of Gemini). + // TODO(#729): It should be possible to reuse the opening challenge from Gemini rather than generate a new one. + auto kappa = transcript.get_challenge("kappa"); + auto prev_aggregate_ecc_op_transcript = instance->proving_key->op_queue->get_previous_aggregate_transcript(); + auto aggregate_ecc_op_transcript = instance->proving_key->op_queue->get_aggregate_transcript(); + std::array prev_agg_op_queue_evals; + std::array right_shifted_op_wire_evals; + std::array agg_op_queue_evals; + std::array prev_agg_op_queue_polynomials; + std::array agg_op_queue_polynomials; + for (size_t idx = 0; idx < Flavor::NUM_WIRES; ++idx) { + std::string suffix = std::to_string(idx + 1); + + // Compute evaluation T_{i-1}(\kappa) + prev_agg_op_queue_polynomials[idx] = Polynomial(prev_aggregate_ecc_op_transcript[idx]); + prev_agg_op_queue_evals[idx] = prev_agg_op_queue_polynomials[idx].evaluate(kappa); + transcript.send_to_verifier("prev_agg_op_queue_eval_" + suffix, prev_agg_op_queue_evals[idx]); + + // Compute evaluation t_i^{shift}(\kappa) + right_shifted_op_wire_evals[idx] = right_shifted_op_wires[idx].evaluate(kappa); + transcript.send_to_verifier("op_wire_eval_" + suffix, right_shifted_op_wire_evals[idx]); + + // Compute evaluation T_i(\kappa) + agg_op_queue_polynomials[idx] = Polynomial(aggregate_ecc_op_transcript[idx]); + agg_op_queue_evals[idx] = agg_op_queue_polynomials[idx].evaluate(kappa); + transcript.send_to_verifier("agg_op_queue_eval_" + suffix, agg_op_queue_evals[idx]); + } + + // Add univariate opening claims for each polynomial. + for (size_t idx = 0; idx < Flavor::NUM_WIRES; ++idx) { + univariate_openings.opening_pairs.emplace_back(OpenPair{ kappa, prev_agg_op_queue_evals[idx] }); + univariate_openings.witnesses.emplace_back(std::move(prev_agg_op_queue_polynomials[idx])); + } + for (size_t idx = 0; idx < Flavor::NUM_WIRES; ++idx) { + univariate_openings.opening_pairs.emplace_back(OpenPair{ kappa, right_shifted_op_wire_evals[idx] }); + univariate_openings.witnesses.emplace_back(std::move(right_shifted_op_wires[idx])); + } + for (size_t idx = 0; idx < Flavor::NUM_WIRES; ++idx) { + univariate_openings.opening_pairs.emplace_back(OpenPair{ kappa, agg_op_queue_evals[idx] }); + univariate_openings.witnesses.emplace_back(std::move(agg_op_queue_polynomials[idx])); + } + } +} + /** * - Do Fiat-Shamir to get "nu" challenge. * - Compute commitment [Q]_1 @@ -174,8 +277,8 @@ template void UltraProver_::execute_shplonk_batched { nu_challenge = transcript.get_challenge("Shplonk:nu"); - batched_quotient_Q = - Shplonk::compute_batched_quotient(gemini_output.opening_pairs, gemini_output.witnesses, nu_challenge); + batched_quotient_Q = Shplonk::compute_batched_quotient( + univariate_openings.opening_pairs, univariate_openings.witnesses, nu_challenge); // commit to Q(X) and add [Q] to the transcript queue.add_commitment(batched_quotient_Q, "Shplonk:Q"); @@ -189,8 +292,11 @@ template void UltraProver_::execute_shplonk_partial { const FF z_challenge = transcript.get_challenge("Shplonk:z"); - shplonk_output = Shplonk::compute_partially_evaluated_batched_quotient( - gemini_output.opening_pairs, gemini_output.witnesses, std::move(batched_quotient_Q), nu_challenge, z_challenge); + shplonk_output = Shplonk::compute_partially_evaluated_batched_quotient(univariate_openings.opening_pairs, + univariate_openings.witnesses, + std::move(batched_quotient_Q), + nu_challenge, + z_challenge); } /** * - Compute final PCS opening proof: @@ -240,6 +346,9 @@ template plonk::proof& UltraProver_::construct_proo // Compute Fold evaluations execute_pcs_evaluation_round(); + // ECC op queue transcript aggregation + execute_op_queue_transcript_aggregation_round(); + // Fiat-Shamir: nu // Compute Shplonk batched quotient commitment Q execute_shplonk_batched_quotient_round(); diff --git a/barretenberg/cpp/src/barretenberg/honk/proof_system/ultra_prover.hpp b/barretenberg/cpp/src/barretenberg/honk/proof_system/ultra_prover.hpp index 9012f2ce26a6..43388fa380a0 100644 --- a/barretenberg/cpp/src/barretenberg/honk/proof_system/ultra_prover.hpp +++ b/barretenberg/cpp/src/barretenberg/honk/proof_system/ultra_prover.hpp @@ -15,6 +15,7 @@ namespace proof_system::honk { template class UltraProver_ { using FF = typename Flavor::FF; + using Commitment = typename Flavor::Commitment; using PCS = typename Flavor::PCS; using CommitmentKey = typename Flavor::CommitmentKey; using ProvingKey = typename Flavor::ProvingKey; @@ -23,6 +24,7 @@ template class UltraProver_ { using CommitmentLabels = typename Flavor::CommitmentLabels; using Curve = typename Flavor::Curve; using Instance = ProverInstance_; + using OpenPair = pcs::OpeningPair; public: explicit UltraProver_(std::shared_ptr); @@ -33,6 +35,7 @@ template class UltraProver_ { void execute_relation_check_rounds(); void execute_univariatization_round(); void execute_pcs_evaluation_round(); + void execute_op_queue_transcript_aggregation_round(); void execute_shplonk_batched_quotient_round(); void execute_shplonk_partial_evaluation_round(); void execute_final_pcs_round(); @@ -60,7 +63,7 @@ template class UltraProver_ { std::shared_ptr instance; sumcheck::SumcheckOutput sumcheck_output; - pcs::gemini::ProverOutput gemini_output; + pcs::gemini::ProverOutput univariate_openings; pcs::shplonk::ProverOutput shplonk_output; std::shared_ptr pcs_commitment_key; diff --git a/barretenberg/cpp/src/barretenberg/honk/proof_system/ultra_verifier.cpp b/barretenberg/cpp/src/barretenberg/honk/proof_system/ultra_verifier.cpp index 0c8ce6d72b22..19bfcab0a41c 100644 --- a/barretenberg/cpp/src/barretenberg/honk/proof_system/ultra_verifier.cpp +++ b/barretenberg/cpp/src/barretenberg/honk/proof_system/ultra_verifier.cpp @@ -1,5 +1,6 @@ #include "./ultra_verifier.hpp" #include "barretenberg/honk/flavor/standard.hpp" +#include "barretenberg/honk/pcs/claim.hpp" #include "barretenberg/honk/transcript/transcript.hpp" #include "barretenberg/honk/utils/power_polynomial.hpp" #include "barretenberg/numeric/bitop/get_msb.hpp" @@ -157,16 +158,65 @@ template bool UltraVerifier_::verify_proof(const plonk // Produce a Gemini claim consisting of: // - d+1 commitments [Fold_{r}^(0)], [Fold_{-r}^(0)], and [Fold^(l)], l = 1:d-1 // - d+1 evaluations a_0_pos, and a_l, l = 0:d-1 - auto gemini_claim = Gemini::reduce_verification(multivariate_challenge, - batched_evaluation, - batched_commitment_unshifted, - batched_commitment_to_be_shifted, - transcript); + auto univariate_opening_claims = Gemini::reduce_verification(multivariate_challenge, + batched_evaluation, + batched_commitment_unshifted, + batched_commitment_to_be_shifted, + transcript); + + // Perform ECC op queue transcript aggregation protocol + if constexpr (IsGoblinFlavor) { + // Receive commitments [t_i^{shift}], [T_{i-1}], and [T_i] + std::array prev_agg_op_queue_commitments; + std::array shifted_op_wire_commitments; + std::array agg_op_queue_commitments; + for (size_t idx = 0; idx < Flavor::NUM_WIRES; ++idx) { + prev_agg_op_queue_commitments[idx] = + transcript.template receive_from_prover("PREV_AGG_OP_QUEUE_" + std::to_string(idx + 1)); + shifted_op_wire_commitments[idx] = + transcript.template receive_from_prover("SHIFTED_OP_WIRE_" + std::to_string(idx + 1)); + agg_op_queue_commitments[idx] = + transcript.template receive_from_prover("AGG_OP_QUEUE_" + std::to_string(idx + 1)); + } + + // Receive transcript poly evaluations + FF kappa = transcript.get_challenge("kappa"); + std::array prev_agg_op_queue_evals; + std::array shifted_op_wire_evals; + std::array agg_op_queue_evals; + for (size_t idx = 0; idx < Flavor::NUM_WIRES; ++idx) { + prev_agg_op_queue_evals[idx] = + transcript.template receive_from_prover("prev_agg_op_queue_eval_" + std::to_string(idx + 1)); + shifted_op_wire_evals[idx] = + transcript.template receive_from_prover("op_wire_eval_" + std::to_string(idx + 1)); + agg_op_queue_evals[idx] = + transcript.template receive_from_prover("agg_op_queue_eval_" + std::to_string(idx + 1)); + + // Check the identity T_i(\kappa) = T_{i-1}(\kappa) + t_i^{shift}(\kappa). If it fails, return false + if (agg_op_queue_evals[idx] != prev_agg_op_queue_evals[idx] + shifted_op_wire_evals[idx]) { + return false; + } + } + + // Add corresponding univariate opening claims {(\kappa, p(\kappa), [p(X)]} + for (size_t idx = 0; idx < Flavor::NUM_WIRES; ++idx) { + univariate_opening_claims.emplace_back(pcs::OpeningClaim{ { kappa, prev_agg_op_queue_evals[idx] }, + prev_agg_op_queue_commitments[idx] }); + } + for (size_t idx = 0; idx < Flavor::NUM_WIRES; ++idx) { + univariate_opening_claims.emplace_back( + pcs::OpeningClaim{ { kappa, shifted_op_wire_evals[idx] }, shifted_op_wire_commitments[idx] }); + } + for (size_t idx = 0; idx < Flavor::NUM_WIRES; ++idx) { + univariate_opening_claims.emplace_back( + pcs::OpeningClaim{ { kappa, agg_op_queue_evals[idx] }, agg_op_queue_commitments[idx] }); + } + } // Produce a Shplonk claim: commitment [Q] - [Q_z], evaluation zero (at random challenge z) - auto shplonk_claim = Shplonk::reduce_verification(pcs_verification_key, gemini_claim, transcript); + auto shplonk_claim = Shplonk::reduce_verification(pcs_verification_key, univariate_opening_claims, transcript); - // // Verify the Shplonk claim with KZG or IPA + // Verify the Shplonk claim with KZG or IPA return PCS::verify(pcs_verification_key, shplonk_claim, transcript); } diff --git a/barretenberg/cpp/src/barretenberg/honk/sumcheck/relation_correctness.test.cpp b/barretenberg/cpp/src/barretenberg/honk/sumcheck/relation_correctness.test.cpp index 73a0f5ebe61d..1f86e061916a 100644 --- a/barretenberg/cpp/src/barretenberg/honk/sumcheck/relation_correctness.test.cpp +++ b/barretenberg/cpp/src/barretenberg/honk/sumcheck/relation_correctness.test.cpp @@ -325,7 +325,7 @@ TEST_F(RelationCorrectnessTests, GoblinUltraRelationCorrectness) // Create a composer and then add an assortment of gates designed to ensure that the constraint(s) represented // by each relation are non-trivially exercised. - auto builder = proof_system::UltraCircuitBuilder(); + auto builder = proof_system::GoblinUltraCircuitBuilder(); // Create an assortment of representative gates create_some_add_gates(builder); diff --git a/barretenberg/cpp/src/barretenberg/honk/sumcheck/sumcheck.hpp b/barretenberg/cpp/src/barretenberg/honk/sumcheck/sumcheck.hpp index e3702752e406..7723864f4b03 100644 --- a/barretenberg/cpp/src/barretenberg/honk/sumcheck/sumcheck.hpp +++ b/barretenberg/cpp/src/barretenberg/honk/sumcheck/sumcheck.hpp @@ -206,6 +206,7 @@ template class SumcheckVerifier { round.compute_next_target_sum(round_univariate, round_challenge); pow_univariate.partially_evaluate(round_challenge); + // TODO(#726): Properly handle this in the recursive setting. if (!verified) { return std::nullopt; } diff --git a/barretenberg/cpp/src/barretenberg/honk/sumcheck/sumcheck_round.hpp b/barretenberg/cpp/src/barretenberg/honk/sumcheck/sumcheck_round.hpp index 1847771a49cb..ae2dfc36ac79 100644 --- a/barretenberg/cpp/src/barretenberg/honk/sumcheck/sumcheck_round.hpp +++ b/barretenberg/cpp/src/barretenberg/honk/sumcheck/sumcheck_round.hpp @@ -425,6 +425,8 @@ template class SumcheckVerifierRound { // with a simulated builder. bool sumcheck_round_failed(false); if constexpr (IsRecursiveFlavor) { + // TODO(#726): Need to constrain this equality and update the native optional return value mechanism for the + // recursive setting. sumcheck_round_failed = (target_total_sum != total_sum).get_value(); } else { sumcheck_round_failed = (target_total_sum != total_sum); diff --git a/barretenberg/cpp/src/barretenberg/polynomials/polynomial.cpp b/barretenberg/cpp/src/barretenberg/polynomials/polynomial.cpp index 47d57d8b986d..c644b778d310 100644 --- a/barretenberg/cpp/src/barretenberg/polynomials/polynomial.cpp +++ b/barretenberg/cpp/src/barretenberg/polynomials/polynomial.cpp @@ -63,6 +63,7 @@ Polynomial::Polynomial(std::span coefficients) memcpy(static_cast(coefficients_.get()), static_cast(coefficients.data()), sizeof(Fr) * coefficients.size()); + zero_memory_beyond(size_); } template @@ -267,6 +268,39 @@ Fr Polynomial::evaluate_from_fft(const EvaluationDomain& large_domain, return polynomial_arithmetic::evaluate_from_fft(coefficients_.get(), large_domain, z, small_domain); } +// TODO(#723): This method is used for the transcript aggregation protocol. For convenience we currently enforce that +// the shift is the same size as the input but this does not need to be the case. Revisit the logic/assertions in this +// method when that issue is addressed. +template void Polynomial::set_to_right_shifted(std::span coeffs_in, size_t shift_size) +{ + // Ensure we're not trying to shift self + ASSERT(coefficients_.get() != coeffs_in.data()); + + auto size_in = coeffs_in.size(); + ASSERT(size_in > 0); + + // Ensure that the last shift_size-many input coefficients are zero to ensure no information is lost in the shift. + ASSERT(shift_size <= size_in); + for (size_t i = 0; i < shift_size; ++i) { + size_t idx = size_in - shift_size - 1; + ASSERT(coeffs_in[idx].is_zero()); + } + + // Set size of self equal to size of input and allocate memory + size_ = size_in; + coefficients_ = allocate_aligned_memory(sizeof(Fr) * capacity()); + + // Zero out the first shift_size-many coefficients of self + memset(static_cast(coefficients_.get()), 0, sizeof(Fr) * shift_size); + + // Copy all but the last shift_size many input coeffs into self at the shift_size-th index. + std::size_t num_to_copy = size_ - shift_size; + memcpy(static_cast(coefficients_.get() + shift_size), + static_cast(coeffs_in.data()), + sizeof(Fr) * num_to_copy); + zero_memory_beyond(size_); +} + template void Polynomial::add_scaled(std::span other, Fr scaling_factor) { const size_t other_size = other.size(); diff --git a/barretenberg/cpp/src/barretenberg/polynomials/polynomial.hpp b/barretenberg/cpp/src/barretenberg/polynomials/polynomial.hpp index 7b9be45c8e8c..f26ac1e69f9f 100644 --- a/barretenberg/cpp/src/barretenberg/polynomials/polynomial.hpp +++ b/barretenberg/cpp/src/barretenberg/polynomials/polynomial.hpp @@ -149,6 +149,18 @@ template class Polynomial { return std::span{ coefficients_.get() + 1, size_ }; } + /** + * @brief Set self to the right shift of input coefficients + * @details Set the size of self to match the input then set coefficients equal to right shift of input. Note: The + * shifted result is constructed with its first shift-many coefficients equal to zero, so we assert that the last + * shift-size many input coefficients are equal to zero to ensure that the relationship f(X) = f_{shift}(X)/X^m + * holds. This is analagous to asserting the first coefficient is 0 in our left-shift-by-one method. + * + * @param coeffs_in + * @param shift_size + */ + void set_to_right_shifted(std::span coeffs_in, size_t shift_size = 1); + /** * @brief adds the polynomial q(X) 'other', multiplied by a scaling factor. * diff --git a/barretenberg/cpp/src/barretenberg/polynomials/polynomial_arithmetic.test.cpp b/barretenberg/cpp/src/barretenberg/polynomials/polynomial_arithmetic.test.cpp index 5bb675a20642..e462c1d2ca16 100644 --- a/barretenberg/cpp/src/barretenberg/polynomials/polynomial_arithmetic.test.cpp +++ b/barretenberg/cpp/src/barretenberg/polynomials/polynomial_arithmetic.test.cpp @@ -11,6 +11,25 @@ using namespace barretenberg; +/** + * @brief Ensure evaluate() gives consistent result for polynomials of different size but same non-zero coefficients. + */ +TEST(polynomials, evaluate) +{ + auto poly1 = polynomial(15); // non power of 2 + auto poly2 = polynomial(64); + for (size_t i = 0; i < poly1.size(); ++i) { + poly1[i] = fr::random_element(); + poly2[i] = poly1[i]; + } + + auto challenge = fr::random_element(); + auto eval1 = poly1.evaluate(challenge); + auto eval2 = poly2.evaluate(challenge); + + EXPECT_EQ(eval1, eval2); +} + TEST(polynomials, fft_with_small_degree) { constexpr size_t n = 16; @@ -1216,3 +1235,36 @@ TYPED_TEST(PolynomialTests, default_construct_then_assign) } EXPECT_EQ(poly.size(), interesting_poly.size()); } + +/** + * @brief Test the right shift functionality of the polynomial class + * + */ +TYPED_TEST(PolynomialTests, RightShift) +{ + using FF = TypeParam; + + // Define valid parameters for computing a right shifted polynomial + size_t num_coeffs = 32; + size_t num_nonzero_coeffs = 7; + size_t shift_magnitude = 21; + Polynomial poly(num_coeffs); + Polynomial right_shifted_poly(num_coeffs); + + for (size_t idx = 0; idx < num_nonzero_coeffs; ++idx) { + poly[idx] = FF::random_element(); + } + + // evaluate the unshifted polynomial + auto evaluation_point = FF::random_element(); + auto unshifted_evaluation = poly.evaluate(evaluation_point); + + // compute the right shift of the original polynomial and its evaluation + right_shifted_poly.set_to_right_shifted(poly, shift_magnitude); + auto shifted_evaluation = right_shifted_poly.evaluate(evaluation_point); + + // reconstruct the unshifted evaluation using that p^{shift}(X) = p(X)*X^m, where m is the shift magnitude + auto shifted_eval_reconstructed = unshifted_evaluation * evaluation_point.pow(shift_magnitude); + + EXPECT_EQ(shifted_evaluation, shifted_eval_reconstructed); +} diff --git a/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/eccvm/eccvm_circuit_builder.hpp b/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/eccvm/eccvm_circuit_builder.hpp index feeee82f4f6a..119b84100403 100644 --- a/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/eccvm/eccvm_circuit_builder.hpp +++ b/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/eccvm/eccvm_circuit_builder.hpp @@ -38,6 +38,12 @@ template class ECCVMCircuitBuilder { using ScalarMul = proof_system_eccvm::ScalarMul; using RawPolynomials = typename Flavor::RawPolynomials; using Polynomial = barretenberg::Polynomial; + + ECCVMCircuitBuilder() = default; + + ECCVMCircuitBuilder(std::vector vm_operations) + : vm_operations(vm_operations){}; + [[nodiscard]] uint32_t get_number_of_muls() const { uint32_t num_muls = 0; diff --git a/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/goblin_ultra_circuit_builder.cpp b/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/goblin_ultra_circuit_builder.cpp new file mode 100644 index 000000000000..58da2674ec54 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/goblin_ultra_circuit_builder.cpp @@ -0,0 +1,165 @@ +#include "goblin_ultra_circuit_builder.hpp" +#include +#include +#include + +using namespace barretenberg; + +namespace proof_system { + +template void GoblinUltraCircuitBuilder_::finalize_circuit() +{ + UltraCircuitBuilder_::finalize_circuit(); + + // Set internally the current and previous size of the aggregate op queue transcript + op_queue->set_size_data(); +} + +/** + * @brief Ensure all polynomials have at least one non-zero coefficient to avoid commiting to the zero-polynomial + * + * @param in Structure containing variables and witness selectors + */ +// TODO(#423): This function adds valid (but arbitrary) gates to ensure that the circuit which includes +// them will not result in any zero-polynomials. It also ensures that the first coefficient of the wire +// polynomials is zero, which is required for them to be shiftable. +template void GoblinUltraCircuitBuilder_::add_gates_to_ensure_all_polys_are_non_zero() +{ + UltraCircuitBuilder_::add_gates_to_ensure_all_polys_are_non_zero(); +} + +/** + * @brief Add gates for simple point addition (no mul) and add the raw operation data to the op queue + * + * @param point Point to be added into the accumulator + */ +template +ecc_op_tuple GoblinUltraCircuitBuilder_::queue_ecc_add_accum(const barretenberg::g1::affine_element& point) +{ + // Add raw op to queue + op_queue->add_accumulate(point); + + // Decompose operation inputs into width-four form and add ecc op gates + auto op_tuple = decompose_ecc_operands(add_accum_op_idx, point); + populate_ecc_op_wires(op_tuple); + + return op_tuple; +} + +/** + * @brief Add gates for point mul-then-accumulate and add the raw operation data to the op queue + * + * @tparam FF + * @param point + * @param scalar The scalar by which point is multiplied prior to being accumulated + * @return ecc_op_tuple encoding the point and scalar inputs to the mul accum + */ +template +ecc_op_tuple GoblinUltraCircuitBuilder_::queue_ecc_mul_accum(const barretenberg::g1::affine_element& point, + const FF& scalar) +{ + // Add raw op to op queue + op_queue->mul_accumulate(point, scalar); + + // Decompose operation inputs into width-four form and add ecc op gates + auto op_tuple = decompose_ecc_operands(mul_accum_op_idx, point, scalar); + populate_ecc_op_wires(op_tuple); + + return op_tuple; +} + +/** + * @brief Add point equality gates based on the current value of the accumulator internal to the op queue and add the + * raw operation data to the op queue + * + * @return ecc_op_tuple encoding the point to which equality has been asserted + */ +template ecc_op_tuple GoblinUltraCircuitBuilder_::queue_ecc_eq() +{ + // Add raw op to op queue + auto point = op_queue->eq(); + + // Decompose operation inputs into width-four form and add ecc op gates + auto op_tuple = decompose_ecc_operands(equality_op_idx, point); + populate_ecc_op_wires(op_tuple); + + return op_tuple; +} + +/** + * @brief Decompose ecc operands into components, add corresponding variables, return ecc op tuple + * + * @param op_idx Index of op code in variables array + * @param point + * @param scalar + * @return ecc_op_tuple Tuple of indices into variables array used to construct pair of ecc op gates + */ +template +ecc_op_tuple GoblinUltraCircuitBuilder_::decompose_ecc_operands(uint32_t op_idx, + const g1::affine_element& point, + const FF& scalar) +{ + // Decompose point coordinates (Fq) into hi-lo chunks (Fr) + const size_t CHUNK_SIZE = 2 * DEFAULT_NON_NATIVE_FIELD_LIMB_BITS; + auto x_256 = uint256_t(point.x); + auto y_256 = uint256_t(point.y); + auto x_lo = FF(x_256.slice(0, CHUNK_SIZE)); + auto x_hi = FF(x_256.slice(CHUNK_SIZE, CHUNK_SIZE * 2)); + auto y_lo = FF(y_256.slice(0, CHUNK_SIZE)); + auto y_hi = FF(y_256.slice(CHUNK_SIZE, CHUNK_SIZE * 2)); + + // Split scalar into 128 bit endomorphism scalars + FF z_1 = 0; + FF z_2 = 0; + auto converted = scalar.from_montgomery_form(); + FF::split_into_endomorphism_scalars(converted, z_1, z_2); + z_1 = z_1.to_montgomery_form(); + z_2 = z_2.to_montgomery_form(); + + // Populate ultra ops in OpQueue with the decomposed operands + op_queue->ultra_ops[0].emplace_back(this->variables[op_idx]); + op_queue->ultra_ops[1].emplace_back(x_lo); + op_queue->ultra_ops[2].emplace_back(x_hi); + op_queue->ultra_ops[3].emplace_back(y_lo); + + op_queue->ultra_ops[0].emplace_back(this->zero_idx); + op_queue->ultra_ops[1].emplace_back(y_hi); + op_queue->ultra_ops[2].emplace_back(z_1); + op_queue->ultra_ops[3].emplace_back(z_2); + + // Add variables for decomposition and get indices needed for op wires + auto x_lo_idx = this->add_variable(x_lo); + auto x_hi_idx = this->add_variable(x_hi); + auto y_lo_idx = this->add_variable(y_lo); + auto y_hi_idx = this->add_variable(y_hi); + auto z_1_idx = this->add_variable(z_1); + auto z_2_idx = this->add_variable(z_2); + + return { op_idx, x_lo_idx, x_hi_idx, y_lo_idx, y_hi_idx, z_1_idx, z_2_idx }; +} + +/** + * @brief Add ecc operation to queue + * + * @param in Variables array indices corresponding to operation inputs + * @note We dont explicitly set values for the selectors here since their values are fully determined by + * num_ecc_op_gates. E.g. in the composer we can reconstruct q_ecc_op as the indicator on the first num_ecc_op_gates + * indices. All other selectors are simply 0 on this domain. + */ +template void GoblinUltraCircuitBuilder_::populate_ecc_op_wires(const ecc_op_tuple& in) +{ + ecc_op_wire_1.emplace_back(in.op); + ecc_op_wire_2.emplace_back(in.x_lo); + ecc_op_wire_3.emplace_back(in.x_hi); + ecc_op_wire_4.emplace_back(in.y_lo); + + ecc_op_wire_1.emplace_back(this->zero_idx); + ecc_op_wire_2.emplace_back(in.y_hi); + ecc_op_wire_3.emplace_back(in.z_1); + ecc_op_wire_4.emplace_back(in.z_2); + + num_ecc_op_gates += 2; +}; + +template class GoblinUltraCircuitBuilder_; +} // namespace proof_system \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/goblin_ultra_circuit_builder.hpp b/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/goblin_ultra_circuit_builder.hpp new file mode 100644 index 000000000000..7982af217e54 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/goblin_ultra_circuit_builder.hpp @@ -0,0 +1,114 @@ +#pragma once +#include "barretenberg/plonk/proof_system/constants.hpp" +#include "barretenberg/plonk/proof_system/types/polynomial_manifest.hpp" +#include "barretenberg/plonk/proof_system/types/prover_settings.hpp" +#include "barretenberg/polynomials/polynomial.hpp" +#include "barretenberg/proof_system/arithmetization/arithmetization.hpp" +#include "barretenberg/proof_system/op_queue/ecc_op_queue.hpp" +#include "barretenberg/proof_system/plookup_tables/plookup_tables.hpp" +#include "barretenberg/proof_system/plookup_tables/types.hpp" +#include "barretenberg/proof_system/types/merkle_hash_type.hpp" +#include "barretenberg/proof_system/types/pedersen_commitment_type.hpp" +#include "ultra_circuit_builder.hpp" +#include + +namespace proof_system { + +using namespace barretenberg; + +template class GoblinUltraCircuitBuilder_ : public UltraCircuitBuilder_ { + public: + static constexpr std::string_view NAME_STRING = "GoblinUltraArithmetization"; + static constexpr CircuitType CIRCUIT_TYPE = CircuitType::ULTRA; + static constexpr size_t DEFAULT_NON_NATIVE_FIELD_LIMB_BITS = + UltraCircuitBuilder_::DEFAULT_NON_NATIVE_FIELD_LIMB_BITS; + + size_t num_ecc_op_gates = 0; // number of ecc op "gates" (rows); these are placed at the start of the circuit + + // Stores record of ecc operations and performs corresponding native operations internally + std::shared_ptr op_queue; + + // Indices for constant variables corresponding to ECCOpQueue op codes + uint32_t null_op_idx; + uint32_t add_accum_op_idx; + uint32_t mul_accum_op_idx; + uint32_t equality_op_idx; + + using WireVector = std::vector>; + + // Wires storing ecc op queue data; values are indices into the variables array + std::array::NUM_WIRES> ecc_op_wires; + + WireVector& ecc_op_wire_1 = std::get<0>(ecc_op_wires); + WireVector& ecc_op_wire_2 = std::get<1>(ecc_op_wires); + WireVector& ecc_op_wire_3 = std::get<2>(ecc_op_wires); + WireVector& ecc_op_wire_4 = std::get<3>(ecc_op_wires); + + // Functions for adding ECC op queue "gates" + ecc_op_tuple queue_ecc_add_accum(const g1::affine_element& point); + ecc_op_tuple queue_ecc_mul_accum(const g1::affine_element& point, const FF& scalar); + ecc_op_tuple queue_ecc_eq(); + + private: + void populate_ecc_op_wires(const ecc_op_tuple& in); + ecc_op_tuple decompose_ecc_operands(uint32_t op, const g1::affine_element& point, const FF& scalar = FF::zero()); + + public: + GoblinUltraCircuitBuilder_(const size_t size_hint = 0, + std::shared_ptr op_queue_in = std::make_shared()) + : UltraCircuitBuilder_(size_hint) + , op_queue(op_queue_in) + { + // Set indices to constants corresponding to Goblin ECC op codes + null_op_idx = this->zero_idx; + add_accum_op_idx = this->put_constant_variable(FF(EccOpCode::ADD_ACCUM)); + mul_accum_op_idx = this->put_constant_variable(FF(EccOpCode::MUL_ACCUM)); + equality_op_idx = this->put_constant_variable(FF(EccOpCode::EQUALITY)); + }; + GoblinUltraCircuitBuilder_(std::shared_ptr op_queue_in) + : GoblinUltraCircuitBuilder_(0, op_queue_in) + {} + + void finalize_circuit(); + void add_gates_to_ensure_all_polys_are_non_zero(); + + size_t get_num_constant_gates() const override { return 0; } + + /** + * @brief Get the final number of gates in a circuit, which consists of the sum of: + * 1) Current number number of actual gates + * 2) Number of public inputs, as we'll need to add a gate for each of them + * 3) Number of Rom array-associated gates + * 4) Number of range-list associated gates + * 5) Number of non-native field multiplication gates. + * + * @return size_t + */ + size_t get_num_gates() const override + { + auto num_ultra_gates = UltraCircuitBuilder_::get_num_gates(); + return num_ultra_gates + num_ecc_op_gates; + } + + /**x + * @brief Print the number and composition of gates in the circuit + * + */ + virtual void print_num_gates() const override + { + size_t count = 0; + size_t rangecount = 0; + size_t romcount = 0; + size_t ramcount = 0; + size_t nnfcount = 0; + UltraCircuitBuilder_::get_num_gates_split_into_components(count, rangecount, romcount, ramcount, nnfcount); + + size_t total = count + romcount + ramcount + rangecount + num_ecc_op_gates; + std::cout << "gates = " << total << " (arith " << count << ", rom " << romcount << ", ram " << ramcount + << ", range " << rangecount << ", non native field gates " << nnfcount << ", goblin ecc op gates " + << num_ecc_op_gates << "), pubinp = " << this->public_inputs.size() << std::endl; + } +}; +extern template class GoblinUltraCircuitBuilder_; +using GoblinUltraCircuitBuilder = GoblinUltraCircuitBuilder_; +} // namespace proof_system diff --git a/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/goblin_ultra_circuit_builder.test.cpp b/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/goblin_ultra_circuit_builder.test.cpp index 860e1b056be7..5d95b57e39a0 100644 --- a/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/goblin_ultra_circuit_builder.test.cpp +++ b/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/goblin_ultra_circuit_builder.test.cpp @@ -1,5 +1,4 @@ -#include "barretenberg/crypto/generators/generator_data.hpp" -#include "ultra_circuit_builder.hpp" +#include "goblin_ultra_circuit_builder.hpp" #include using namespace barretenberg; @@ -20,7 +19,7 @@ TEST(UltraCircuitBuilder, GoblinSimple) { const size_t CHUNK_SIZE = plonk::NUM_LIMB_BITS_IN_FIELD_SIMULATION * 2; - auto builder = UltraCircuitBuilder(); + auto builder = GoblinUltraCircuitBuilder(); // Compute a simple point accumulation natively auto P1 = g1::affine_element::random_element(); @@ -46,7 +45,7 @@ TEST(UltraCircuitBuilder, GoblinSimple) EXPECT_EQ(P_result_y, uint256_t(P_expected.y)); // Check that the accumulator in the op queue has been reset to 0 - auto accumulator = builder.op_queue.get_accumulator(); + auto accumulator = builder.op_queue->get_accumulator(); EXPECT_EQ(accumulator, g1::affine_point_at_infinity); // Check number of ecc op "gates"/rows = 3 ops * 2 rows per op = 6 @@ -77,4 +76,35 @@ TEST(UltraCircuitBuilder, GoblinSimple) auto P2_y = P2_y_lo + (P2_y_hi << CHUNK_SIZE); EXPECT_EQ(P2_y, uint256_t(P2.y)); } + +/** + * @brief Check that the ultra ops are recorded correctly in the EccOpQueue + * + */ +TEST(UltraCircuitBuilder, GoblinEccOpQueueUltraOps) +{ + // Construct a simple circuit with op gates + auto builder = GoblinUltraCircuitBuilder(); + + // Compute a simple point accumulation natively + auto P1 = g1::affine_element::random_element(); + auto P2 = g1::affine_element::random_element(); + auto z = fr::random_element(); + + // Add gates corresponding to the above operations + builder.queue_ecc_add_accum(P1); + builder.queue_ecc_mul_accum(P2, z); + builder.queue_ecc_eq(); + + // Check that the ultra ops recorded in the EccOpQueue match the ops recorded in the wires + auto ultra_ops = builder.op_queue->get_aggregate_transcript(); + for (size_t i = 1; i < 4; ++i) { + for (size_t j = 0; j < builder.num_ecc_op_gates; ++j) { + auto op_wire_val = builder.variables[builder.ecc_op_wires[i][j]]; + auto ultra_op_val = ultra_ops[i][j]; + ASSERT_EQ(op_wire_val, ultra_op_val); + } + } +} + } // namespace proof_system \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/ultra_circuit_builder.cpp b/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/ultra_circuit_builder.cpp index 3446b5cef878..d88a3c010493 100644 --- a/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/ultra_circuit_builder.cpp +++ b/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/ultra_circuit_builder.cpp @@ -495,128 +495,6 @@ template uint32_t UltraCircuitBuilder_::put_constant_variable( } } -/** - * ** Goblin Methods ** - */ - -/** - * @brief Add gates for simple point addition without scalar and compute corresponding op natively - * - * @param point - */ -template -ecc_op_tuple UltraCircuitBuilder_::queue_ecc_add_accum(const barretenberg::g1::affine_element& point) -{ - // Add raw op to queue - op_queue.add_accumulate(point); - - // Add ecc op gates - auto op_tuple = make_ecc_op_tuple(EccOpCode::ADD_ACCUM, point); - populate_ecc_op_wires(op_tuple); - - return op_tuple; -} - -/** - * @brief Add gates for point mul and add and compute corresponding op natively - * - * @tparam FF - * @param point - * @param scalar - * @return ecc_op_tuple encoding the point and scalar inputs to the mul accum - */ -template -ecc_op_tuple UltraCircuitBuilder_::queue_ecc_mul_accum(const barretenberg::g1::affine_element& point, - const barretenberg::fr& scalar) -{ - // Add raw op to op queue - op_queue.mul_accumulate(point, scalar); - - // Add ecc op gates - auto op_tuple = make_ecc_op_tuple(EccOpCode::MUL_ACCUM, point, scalar); - populate_ecc_op_wires(op_tuple); - - return op_tuple; -} - -/** - * @brief Add point equality gates - * - * @return ecc_op_tuple encoding the point to which equality has been asserted - */ -template ecc_op_tuple UltraCircuitBuilder_::queue_ecc_eq() -{ - // Add raw op to op queue - auto point = op_queue.eq(); - - // Add ecc op gates - auto op_tuple = make_ecc_op_tuple(EccOpCode::EQUALITY, point); - populate_ecc_op_wires(op_tuple); - - return op_tuple; -} - -/** - * @brief Decompose ecc operands into components, add corresponding variables, return ecc op tuple - * - * @param op - * @param point - * @param scalar - * @return ecc_op_tuple Tuple of indices into variables array used to construct pair of ecc op gates - */ -template -ecc_op_tuple UltraCircuitBuilder_::make_ecc_op_tuple(uint32_t op, const g1::affine_element& point, const fr& scalar) -{ - const size_t CHUNK_SIZE = 2 * DEFAULT_NON_NATIVE_FIELD_LIMB_BITS; - auto x_256 = uint256_t(point.x); - auto y_256 = uint256_t(point.y); - auto x_lo_idx = this->add_variable(x_256.slice(0, CHUNK_SIZE)); - auto x_hi_idx = this->add_variable(x_256.slice(CHUNK_SIZE, CHUNK_SIZE * 2)); - auto y_lo_idx = this->add_variable(y_256.slice(0, CHUNK_SIZE)); - auto y_hi_idx = this->add_variable(y_256.slice(CHUNK_SIZE, CHUNK_SIZE * 2)); - - // Split scalar into 128 bit endomorphism scalars - fr z_1 = 0; - fr z_2 = 0; - // TODO(luke): do this montgomery conversion here? - // auto converted = scalar.from_montgomery_form(); - // fr::split_into_endomorphism_scalars(converted, z_1, z_2); - // z_1 = z_1.to_montgomery_form(); - // z_2 = z_2.to_montgomery_form(); - fr::split_into_endomorphism_scalars(scalar, z_1, z_2); - auto z_1_idx = this->add_variable(z_1); - auto z_2_idx = this->add_variable(z_2); - - return { op, x_lo_idx, x_hi_idx, y_lo_idx, y_hi_idx, z_1_idx, z_2_idx }; -} - -/** - * @brief Add ecc operation to queue - * - * @param in Variables array indices corresponding to operation inputs - * @note We dont explicitly set values for the selectors here since their values are fully determined by - * num_ecc_op_gates. E.g. in the composer we can reconstruct q_ecc_op as the indicator on the first num_ecc_op_gates - * indices. All other selectors are simply 0 on this domain. - */ -template void UltraCircuitBuilder_::populate_ecc_op_wires(const ecc_op_tuple& in) -{ - ecc_op_wire_1.emplace_back(in.op); - ecc_op_wire_2.emplace_back(in.x_lo); - ecc_op_wire_3.emplace_back(in.x_hi); - ecc_op_wire_4.emplace_back(in.y_lo); - - ecc_op_wire_1.emplace_back(in.op); // TODO(luke): second op val is sort of a dummy. use "op" again? - ecc_op_wire_2.emplace_back(in.y_hi); - ecc_op_wire_3.emplace_back(in.z_1); - ecc_op_wire_4.emplace_back(in.z_2); - - num_ecc_op_gates += 2; -}; - -/** - * End of Goblin Methods - */ - template plookup::BasicTable& UltraCircuitBuilder_::get_table(const plookup::BasicTableId id) { for (plookup::BasicTable& table : lookup_tables) { diff --git a/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/ultra_circuit_builder.hpp b/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/ultra_circuit_builder.hpp index f34a3a6dbb05..454621c17b02 100644 --- a/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/ultra_circuit_builder.hpp +++ b/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/ultra_circuit_builder.hpp @@ -37,11 +37,6 @@ template class UltraCircuitBuilder_ : public CircuitBuilderBase class UltraCircuitBuilder_ : public CircuitBuilderBase(this->wires); WireVector& w_4 = std::get<3>(this->wires); - // Wires storing ecc op queue data; values are indices into the variables array - std::array::NUM_WIRES> ecc_op_wires; - - WireVector& ecc_op_wire_1 = std::get<0>(ecc_op_wires); - WireVector& ecc_op_wire_2 = std::get<1>(ecc_op_wires); - WireVector& ecc_op_wire_3 = std::get<2>(ecc_op_wires); - WireVector& ecc_op_wire_4 = std::get<3>(ecc_op_wires); - SelectorVector& q_m = this->selectors.q_m; SelectorVector& q_c = this->selectors.q_c; SelectorVector& q_1 = this->selectors.q_1; @@ -703,21 +690,8 @@ template class UltraCircuitBuilder_ : public CircuitBuilderBase class UltraCircuitBuilder_ : public CircuitBuilderBase class UltraCircuitBuilder_ : public CircuitBuilderBase; + Point point_at_infinity = Curve::Group::affine_point_at_infinity; // The operations written to the queue are also performed natively; the result is stored in accumulator Point accumulator = point_at_infinity; public: - std::vector raw_ops; - std::vector> ultra_ops; - std::vector> eccvm_ops; + std::vector raw_ops; + std::array, 4> ultra_ops; // ops encoded in the width-4 Ultra format + + size_t current_ultra_ops_size = 0; // M_i + size_t previous_ultra_ops_size = 0; // M_{i-1} + + std::array ultra_ops_commitments; + std::array previous_ultra_ops_commitments; + + Point get_accumulator() { return accumulator; } + + /** + * @brief Set the current and previous size of the ultra_ops transcript + * + * @details previous_ultra_ops_size = M_{i-1} is needed by the prover to extract the previous aggregate op + * queue transcript T_{i-1} from the current one T_i. This method should be called when a circuit is 'finalized'. + */ + void set_size_data() + { + previous_ultra_ops_size = current_ultra_ops_size; + current_ultra_ops_size = ultra_ops[0].size(); + } - uint32_t get_number_of_muls() + [[nodiscard]] size_t get_previous_size() const { return previous_ultra_ops_size; } + [[nodiscard]] size_t get_current_size() const { return current_ultra_ops_size; } + + void set_commitment_data(std::array& commitments) { - uint32_t num_muls = 0; - for (auto& op : raw_ops) { - if (op.mul) { - if (op.scalar_1 != 0) { - num_muls++; - } - if (op.scalar_2 != 0) { - num_muls++; - } - } + previous_ultra_ops_commitments = ultra_ops_commitments; + ultra_ops_commitments = commitments; + } + + /** + * @brief Get a 'view' of the current ultra ops object + * + * @return std::vector> + */ + std::vector> get_aggregate_transcript() + { + std::vector> result; + result.reserve(ultra_ops.size()); + for (auto& entry : ultra_ops) { + result.emplace_back(entry); } - return num_muls; + return result; } - Point get_accumulator() { return accumulator; } + /** + * @brief Get a 'view' of the previous ultra ops object + * + * @return std::vector> + */ + std::vector> get_previous_aggregate_transcript() + { + std::vector> result; + result.reserve(ultra_ops.size()); + // Construct T_{i-1} as a view of size M_{i-1} into T_i + for (auto& entry : ultra_ops) { + result.emplace_back(entry.begin(), previous_ultra_ops_size); + } + return result; + } + + /** + * @brief TESTING PURPOSES ONLY: Populate ECC op queue with mock data as stand in for "previous circuit" in tests + * @details TODO(#723): We currently cannot support Goblin proofs (specifically, transcript aggregation) if there + * is not existing data in the ECC op queue (since this leads to zero-commitment issues). This method populates the + * op queue with mock data so that the prover of an arbitrary 'first' circuit can behave as if it were not the + * prover over the first circuit in the stack. This method should be removed entirely once this is resolved. + * + * @param op_queue + */ + void populate_with_mock_initital_data() + { + // Add a single row of data to the op queue and commit to each column as [1] * FF(data) + std::array mock_op_queue_commitments; + size_t idx = 0; + for (auto& entry : this->ultra_ops) { + auto mock_data = Fr::random_element(); + entry.emplace_back(mock_data); + mock_op_queue_commitments[idx++] = Point::one() * mock_data; + } + // Set some internal data based on the size of the op queue data + this->set_size_data(); + // Add the commitments to the op queue data for use by the next circuit + this->set_commitment_data(mock_op_queue_commitments); + } /** * @brief Write point addition op to queue and natively perform addition @@ -71,14 +126,14 @@ class ECCOpQueue { accumulator = accumulator + to_add; // Store the operation - raw_ops.emplace_back(ECCOp{ + raw_ops.emplace_back(ECCVMOperation{ .add = true, .mul = false, .eq = false, .reset = false, .base_point = to_add, - .scalar_1 = 0, - .scalar_2 = 0, + .z1 = 0, + .z2 = 0, .mul_scalar_full = 0, }); } @@ -94,20 +149,20 @@ class ECCOpQueue { accumulator = accumulator + to_mul * scalar; // Store the operation - Fr scalar_1 = 0; - Fr scalar_2 = 0; + Fr z1 = 0; + Fr z2 = 0; auto converted = scalar.from_montgomery_form(); - Fr::split_into_endomorphism_scalars(converted, scalar_1, scalar_2); - scalar_1 = scalar_1.to_montgomery_form(); - scalar_2 = scalar_2.to_montgomery_form(); - raw_ops.emplace_back(ECCOp{ + Fr::split_into_endomorphism_scalars(converted, z1, z2); + z1 = z1.to_montgomery_form(); + z2 = z2.to_montgomery_form(); + raw_ops.emplace_back(ECCVMOperation{ .add = false, .mul = true, .eq = false, .reset = false, .base_point = to_mul, - .scalar_1 = scalar_1, - .scalar_2 = scalar_2, + .z1 = z1, + .z2 = z2, .mul_scalar_full = scalar, }); } @@ -122,14 +177,14 @@ class ECCOpQueue { auto expected = accumulator; accumulator.self_set_infinity(); // TODO(luke): is this always desired? - raw_ops.emplace_back(ECCOp{ + raw_ops.emplace_back(ECCVMOperation{ .add = false, .mul = false, .eq = true, .reset = true, .base_point = expected, - .scalar_1 = 0, - .scalar_2 = 0, + .z1 = 0, + .z2 = 0, .mul_scalar_full = 0, }); @@ -142,14 +197,14 @@ class ECCOpQueue { */ void empty_row() { - raw_ops.emplace_back(ECCOp{ + raw_ops.emplace_back(ECCVMOperation{ .add = false, .mul = false, .eq = false, .reset = false, .base_point = point_at_infinity, - .scalar_1 = 0, - .scalar_2 = 0, + .z1 = 0, + .z2 = 0, .mul_scalar_full = 0, }); } diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/biggroup/biggroup.hpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/biggroup/biggroup.hpp index a405d5545566..ce2f64f9fee2 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/biggroup/biggroup.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/biggroup/biggroup.hpp @@ -176,7 +176,6 @@ template class element { // only works with Plookup! template static element wnaf_batch_mul(const std::vector& points, const std::vector& scalars); - template static element batch_mul(const std::vector& points, const std::vector& scalars, const size_t max_num_bits = 0); diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/biggroup/biggroup_goblin.hpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/biggroup/biggroup_goblin.hpp index 8217b08c2a18..675ed03396d6 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/biggroup/biggroup_goblin.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/biggroup/biggroup_goblin.hpp @@ -34,7 +34,7 @@ element element::goblin_batch_mul(const std::vector< auto builder = points[0].get_context(); // Check that the internal accumulator is zero? - ASSERT(builder->op_queue.get_accumulator().is_point_at_infinity()); + ASSERT(builder->op_queue->get_accumulator().is_point_at_infinity()); // Loop over all points and scalars size_t num_points = points.size(); diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/biggroup/biggroup_goblin.test.cpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/biggroup/biggroup_goblin.test.cpp index 7f6ff8a5d726..04f61ef7843c 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/biggroup/biggroup_goblin.test.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/biggroup/biggroup_goblin.test.cpp @@ -41,7 +41,6 @@ template class stdlib_biggroup_goblin : public testing::Test { */ static void test_goblin_style_batch_mul() { - const bool goblin_flag = true; // used to indicate goblin-style in batch_mul const size_t num_points = 5; Builder builder; @@ -59,7 +58,7 @@ template class stdlib_biggroup_goblin : public testing::Test { circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalars[i])); } - element_ct result_point = element_ct::template batch_mul(circuit_points, circuit_scalars); + element_ct result_point = element_ct::batch_mul(circuit_points, circuit_scalars); element expected_point = g1::one; expected_point.self_set_infinity(); @@ -78,7 +77,7 @@ template class stdlib_biggroup_goblin : public testing::Test { } }; -using TestTypes = testing::Types>; +using TestTypes = testing::Types>; TYPED_TEST_SUITE(stdlib_biggroup_goblin, TestTypes); diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/biggroup/biggroup_impl.hpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/biggroup/biggroup_impl.hpp index efc05ef40572..c62ffea796c4 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/biggroup/biggroup_impl.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/biggroup/biggroup_impl.hpp @@ -597,14 +597,14 @@ std::pair, element> element::c * scalars See `bn254_endo_batch_mul` for description of algorithm **/ template -template element element::batch_mul(const std::vector& points, const std::vector& scalars, const size_t max_num_bits) { - if constexpr (use_goblin) { + if constexpr (IsGoblinBuilder) { return goblin_batch_mul(points, scalars); } + const size_t num_points = points.size(); ASSERT(scalars.size() == num_points); batch_lookup_table point_table(points); diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/circuit_builders/circuit_builders.hpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/circuit_builders/circuit_builders.hpp index c4ef70691d03..d12df90869fe 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/circuit_builders/circuit_builders.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/circuit_builders/circuit_builders.hpp @@ -3,27 +3,35 @@ * templates. */ #pragma once +#include "barretenberg/proof_system/circuit_builder/goblin_ultra_circuit_builder.hpp" #include "barretenberg/proof_system/circuit_builder/standard_circuit_builder.hpp" #include "barretenberg/proof_system/circuit_builder/turbo_circuit_builder.hpp" #include "barretenberg/proof_system/circuit_builder/ultra_circuit_builder.hpp" template -concept HasPlookup = proof_system::IsAnyOf; +concept HasPlookup = + proof_system::IsAnyOf; + +template +concept IsGoblinBuilder = proof_system::IsAnyOf; #define INSTANTIATE_STDLIB_METHOD(stdlib_method) \ template stdlib_method(proof_system::StandardCircuitBuilder); \ template stdlib_method(proof_system::TurboCircuitBuilder); \ - template stdlib_method(proof_system::UltraCircuitBuilder); + template stdlib_method(proof_system::UltraCircuitBuilder); \ + template stdlib_method(proof_system::GoblinUltraCircuitBuilder); #define INSTANTIATE_STDLIB_TYPE(stdlib_type) \ template class stdlib_type; \ template class stdlib_type; \ - template class stdlib_type; + template class stdlib_type; \ + template class stdlib_type; #define INSTANTIATE_STDLIB_TYPE_VA(stdlib_type, ...) \ template class stdlib_type; \ template class stdlib_type; \ - template class stdlib_type; + template class stdlib_type; \ + template class stdlib_type; #define INSTANTIATE_STDLIB_BASIC_TYPE(stdlib_type) \ template class stdlib_type; \ @@ -33,9 +41,14 @@ concept HasPlookup = proof_system::IsAnyOf template class stdlib_type; \ template class stdlib_type; -#define INSTANTIATE_STDLIB_ULTRA_METHOD(stdlib_method) template stdlib_method(proof_system::UltraCircuitBuilder); +#define INSTANTIATE_STDLIB_ULTRA_METHOD(stdlib_method) \ + template stdlib_method(proof_system::UltraCircuitBuilder); \ + template stdlib_method(proof_system::GoblinUltraCircuitBuilder); -#define INSTANTIATE_STDLIB_ULTRA_TYPE(stdlib_type) template class stdlib_type; +#define INSTANTIATE_STDLIB_ULTRA_TYPE(stdlib_type) \ + template class stdlib_type; \ + template class stdlib_type; #define INSTANTIATE_STDLIB_ULTRA_TYPE_VA(stdlib_type, ...) \ - template class stdlib_type; + template class stdlib_type; \ + template class stdlib_type; diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/circuit_builders/circuit_builders_fwd.hpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/circuit_builders/circuit_builders_fwd.hpp index 52a83dda9a4a..197026406d45 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/circuit_builders/circuit_builders_fwd.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/circuit_builders/circuit_builders_fwd.hpp @@ -29,22 +29,27 @@ template class TurboCircuitBuilder_; using TurboCircuitBuilder = TurboCircuitBuilder_>; template class UltraCircuitBuilder_; using UltraCircuitBuilder = UltraCircuitBuilder_>; +template class GoblinUltraCircuitBuilder_; +using GoblinUltraCircuitBuilder = GoblinUltraCircuitBuilder_>; } // namespace proof_system #define EXTERN_STDLIB_TYPE(stdlib_type) \ extern template class stdlib_type; \ extern template class stdlib_type; \ - extern template class stdlib_type; + extern template class stdlib_type; \ + extern template class stdlib_type; #define EXTERN_STDLIB_METHOD(stdlib_method) \ extern template stdlib_method(proof_system::StandardCircuitBuilder); \ extern template stdlib_method(proof_system::TurboCircuitBuilder); \ - extern template stdlib_method(proof_system::UltraCircuitBuilder); + extern template stdlib_method(proof_system::UltraCircuitBuilder); \ + extern template stdlib_method(proof_system::GoblinUltraCircuitBuilder); #define EXTERN_STDLIB_TYPE_VA(stdlib_type, ...) \ extern template class stdlib_type; \ extern template class stdlib_type; \ - extern template class stdlib_type; + extern template class stdlib_type; \ + extern template class stdlib_type; #define EXTERN_STDLIB_BASIC_TYPE(stdlib_type) \ extern template class stdlib_type; \ @@ -54,9 +59,14 @@ using UltraCircuitBuilder = UltraCircuitBuilder_; \ extern template class stdlib_type; -#define EXTERN_STDLIB_ULTRA_TYPE(stdlib_type) extern template class stdlib_type; +#define EXTERN_STDLIB_ULTRA_TYPE(stdlib_type) \ + extern template class stdlib_type; \ + extern template class stdlib_type; #define EXTERN_STDLIB_ULTRA_TYPE_VA(stdlib_type, ...) \ - extern template class stdlib_type; + extern template class stdlib_type; \ + extern template class stdlib_type; -#define EXTERN_STDLIB_ULTRA_METHOD(stdlib_method) extern template stdlib_method(proof_system::UltraCircuitBuilder); +#define EXTERN_STDLIB_ULTRA_METHOD(stdlib_method) \ + extern template stdlib_method(proof_system::UltraCircuitBuilder); \ + extern template stdlib_method(proof_system::GoblinUltraCircuitBuilder); diff --git a/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/transcript/transcript.test.cpp b/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/transcript/transcript.test.cpp index 9611368117ce..b91139f7fbf0 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/transcript/transcript.test.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/transcript/transcript.test.cpp @@ -12,7 +12,7 @@ namespace proof_system::plonk::stdlib::recursion::honk { using Builder = UltraCircuitBuilder; using UltraFlavor = ::proof_system::honk::flavor::Ultra; -using UltraRecursiveFlavor = ::proof_system::honk::flavor::UltraRecursive; +using UltraRecursiveFlavor = ::proof_system::honk::flavor::UltraRecursive_; using FF = barretenberg::fr; using ProverTranscript = ::proof_system::honk::ProverTranscript; using VerifierTranscript = ::proof_system::honk::VerifierTranscript; diff --git a/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/verifier/goblin_verifier.test.cpp b/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/verifier/goblin_verifier.test.cpp new file mode 100644 index 000000000000..a74ea38d1f89 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/verifier/goblin_verifier.test.cpp @@ -0,0 +1,221 @@ +#include "barretenberg/common/test.hpp" +#include "barretenberg/honk/composer/ultra_composer.hpp" +#include "barretenberg/honk/flavor/ultra_recursive.hpp" +#include "barretenberg/honk/proof_system/ultra_verifier.hpp" +#include "barretenberg/stdlib/commitment/pedersen/pedersen.hpp" +#include "barretenberg/stdlib/hash/blake3s/blake3s.hpp" +#include "barretenberg/stdlib/primitives/curves/bn254.hpp" +#include "barretenberg/stdlib/recursion/honk/verifier/ultra_recursive_verifier.hpp" + +namespace proof_system::plonk::stdlib::recursion::honk { + +/** + * @brief Test suite for recursive verification of conventional Ultra Honk proofs + * @details The recursive verification circuit is arithmetized in two different ways: 1) using the conventional Ultra + * arithmetization (UltraCircuitBuilder), or 2) a Goblin-style Ultra arithmetization (GoblinUltraCircuitBuilder). + * + * @tparam Builder Circuit builder for the recursive verifier circuit + */ +template class GoblinRecursiveVerifierTest : public testing::Test { + + // Define types relevant for inner circuit + using Flavor = ::proof_system::honk::flavor::GoblinUltra; + using InnerComposer = ::proof_system::honk::UltraComposer_; + using InnerBuilder = typename InnerComposer::CircuitBuilder; + using NativeVerifier = ::proof_system::honk::UltraVerifier_<::proof_system::honk::flavor::Ultra>; + using InnerCurve = bn254; + + // Types for recursive verifier circuit + using RecursiveFlavor = ::proof_system::honk::flavor::GoblinUltraRecursive_; + using RecursiveVerifier = UltraRecursiveVerifier_; + using OuterBuilder = BuilderType; + using VerificationKey = typename RecursiveVerifier::VerificationKey; + + /** + * @brief Create a non-trivial arbitrary inner circuit, the proof of which will be recursively verified + * + * @param builder + * @param public_inputs + * @param log_num_gates + */ + static InnerBuilder create_inner_circuit(size_t log_num_gates = 10) + { + using fr_ct = InnerCurve::ScalarField; + using fq_ct = InnerCurve::BaseField; + using point_ct = InnerCurve::AffineElement; + using public_witness_ct = InnerCurve::public_witness_ct; + using witness_ct = InnerCurve::witness_ct; + using byte_array_ct = InnerCurve::byte_array_ct; + using fr = typename InnerCurve::ScalarFieldNative; + using point = typename InnerCurve::GroupNative::affine_element; + + // Instantiate ECC op queue and add mock data to simulate interaction with a previous circuit + auto op_queue = std::make_shared(); + op_queue->populate_with_mock_initital_data(); + + InnerBuilder builder(op_queue); + + // Create 2^log_n many add gates based on input log num gates + const size_t num_gates = 1 << log_num_gates; + for (size_t i = 0; i < num_gates; ++i) { + fr a = fr::random_element(); + uint32_t a_idx = builder.add_variable(a); + + fr b = fr::random_element(); + fr c = fr::random_element(); + fr d = a + b + c; + 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) }); + } + + // Add some arbitrary goblin-style ECC op gates via a batch mul + size_t num_points = 5; + std::vector circuit_points; + std::vector circuit_scalars; + for (size_t i = 0; i < num_points; ++i) { + circuit_points.push_back(point_ct::from_witness(&builder, point::random_element())); + circuit_scalars.push_back(fr_ct::from_witness(&builder, fr::random_element())); + } + point_ct::batch_mul(circuit_points, circuit_scalars); + + // Define some additional arbitrary convetional circuit logic + fr_ct a(public_witness_ct(&builder, fr::random_element())); + fr_ct b(public_witness_ct(&builder, fr::random_element())); + fr_ct c(public_witness_ct(&builder, fr::random_element())); + + for (size_t i = 0; i < 32; ++i) { + a = (a * b) + b + a; + a = a.madd(b, c); + } + pedersen_commitment::compress(a, b); + byte_array_ct to_hash(&builder, "nonsense test data"); + blake3s(to_hash); + + fr bigfield_data = fr::random_element(); + fr bigfield_data_a{ bigfield_data.data[0], bigfield_data.data[1], 0, 0 }; + fr bigfield_data_b{ bigfield_data.data[2], bigfield_data.data[3], 0, 0 }; + + fq_ct big_a(fr_ct(witness_ct(&builder, bigfield_data_a.to_montgomery_form())), fr_ct(witness_ct(&builder, 0))); + fq_ct big_b(fr_ct(witness_ct(&builder, bigfield_data_b.to_montgomery_form())), fr_ct(witness_ct(&builder, 0))); + + big_a* big_b; + + return builder; + }; + + public: + static void SetUpTestSuite() { barretenberg::srs::init_crs_factory("../srs_db/ignition"); } + + /** + * @brief Create inner circuit and call check_circuit on it + * + */ + static void test_inner_circuit() + { + auto inner_circuit = create_inner_circuit(); + + bool result = inner_circuit.check_circuit(); + EXPECT_EQ(result, true); + } + + /** + * @brief Instantiate a recursive verification key from the native verification key produced by the inner cicuit + * builder. Check consistency beteen the native and stdlib types. + * + */ + static void test_recursive_verification_key_creation() + { + // Create an arbitrary inner circuit + auto inner_circuit = create_inner_circuit(); + + // Compute native verification key + InnerComposer inner_composer; + auto instance = inner_composer.create_instance(inner_circuit); + auto prover = inner_composer.create_prover(instance); // A prerequisite for computing VK + const auto native_verification_key = instance->compute_verification_key(); + + // Instantiate the recursive verification key from the native verification key + OuterBuilder outer_circuit; + auto verification_key = std::make_shared(&outer_circuit, native_verification_key); + + // Spot check some values in the recursive VK to ensure it was constructed correctly + EXPECT_EQ(verification_key->circuit_size, native_verification_key->circuit_size); + EXPECT_EQ(verification_key->log_circuit_size, native_verification_key->log_circuit_size); + EXPECT_EQ(verification_key->num_public_inputs, native_verification_key->num_public_inputs); + EXPECT_EQ(verification_key->q_m.get_value(), native_verification_key->q_m); + EXPECT_EQ(verification_key->q_r.get_value(), native_verification_key->q_r); + EXPECT_EQ(verification_key->sigma_1.get_value(), native_verification_key->sigma_1); + EXPECT_EQ(verification_key->id_3.get_value(), native_verification_key->id_3); + EXPECT_EQ(verification_key->lagrange_ecc_op.get_value(), native_verification_key->lagrange_ecc_op); + } + + /** + * @brief Construct a recursive verification circuit for the proof of an inner circuit then call check_circuit on it + * + */ + static void test_recursive_verification() + { + // Create an arbitrary inner circuit + auto inner_circuit = create_inner_circuit(); + + // Generate a proof over the inner circuit + InnerComposer inner_composer; + auto instance = inner_composer.create_instance(inner_circuit); + auto inner_prover = inner_composer.create_prover(instance); + auto inner_proof = inner_prover.construct_proof(); + const auto native_verification_key = instance->compute_verification_key(); + + // Create a recursive verification circuit for the proof of the inner circuit + OuterBuilder outer_circuit; + auto verification_key = std::make_shared(&outer_circuit, native_verification_key); + RecursiveVerifier verifier(&outer_circuit, verification_key); + auto pairing_points = verifier.verify_proof(inner_proof); + + // Check the recursive verifier circuit + EXPECT_EQ(outer_circuit.failed(), false) << outer_circuit.err(); + EXPECT_TRUE(outer_circuit.check_circuit()); + + // Additional check 1: Perform native verification then perform the pairing on the outputs of the recursive + // verifier and check that the result agrees. + auto native_verifier = inner_composer.create_verifier(instance); + auto native_result = native_verifier.verify_proof(inner_proof); + auto recursive_result = native_verifier.pcs_verification_key->pairing_check(pairing_points[0].get_value(), + pairing_points[1].get_value()); + EXPECT_EQ(recursive_result, native_result); + + // Additional check 2: Ensure that the underlying native and recursive verification algorithms agree by ensuring + // the manifests produced by each agree. + auto recursive_manifest = verifier.transcript.get_manifest(); + auto native_manifest = native_verifier.transcript.get_manifest(); + // recursive_manifest.print(); + // native_manifest.print(); + for (size_t i = 0; i < recursive_manifest.size(); ++i) { + EXPECT_EQ(recursive_manifest[i], native_manifest[i]); + } + } +}; + +// Run the recursive verifier tests with conventional Ultra builder and Goblin builder +using BuilderTypes = testing::Types; + +TYPED_TEST_SUITE(GoblinRecursiveVerifierTest, BuilderTypes); + +HEAVY_TYPED_TEST(GoblinRecursiveVerifierTest, InnerCircuit) +{ + TestFixture::test_inner_circuit(); +} + +HEAVY_TYPED_TEST(GoblinRecursiveVerifierTest, RecursiveVerificationKey) +{ + TestFixture::test_recursive_verification_key_creation(); +} + +HEAVY_TYPED_TEST(GoblinRecursiveVerifierTest, SingleRecursiveVerification) +{ + TestFixture::test_recursive_verification(); +}; + +} // namespace proof_system::plonk::stdlib::recursion::honk \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/verifier/ultra_recursive_verifier.cpp b/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/verifier/ultra_recursive_verifier.cpp index 323b4a2800f9..0a40e6b3e0a2 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/verifier/ultra_recursive_verifier.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/verifier/ultra_recursive_verifier.cpp @@ -8,9 +8,9 @@ namespace proof_system::plonk::stdlib::recursion::honk { -template -UltraRecursiveVerifier_::UltraRecursiveVerifier_(Builder* builder, - std::shared_ptr verifier_key) +template +UltraRecursiveVerifier_::UltraRecursiveVerifier_(Builder* builder, + std::shared_ptr verifier_key) : key(verifier_key) , builder(builder) {} @@ -19,18 +19,18 @@ UltraRecursiveVerifier_::UltraRecursiveVerifier_(Builder* b * @brief This function constructs a recursive verifier circuit for an Ultra Honk proof of a given flavor. * */ -template -std::array UltraRecursiveVerifier_::verify_proof( - const plonk::proof& proof) +template +std::array UltraRecursiveVerifier_::verify_proof(const plonk::proof& proof) { using Sumcheck = ::proof_system::honk::sumcheck::SumcheckVerifier; using Curve = typename Flavor::Curve; - using Gemini = ::proof_system::honk::pcs::gemini::GeminiVerifier_; - using Shplonk = ::proof_system::honk::pcs::shplonk::ShplonkVerifier_; - using KZG = ::proof_system::honk::pcs::kzg::KZG; // note: This can only be KZG + using Gemini = ::proof_system::honk::pcs::gemini::GeminiVerifier_; + using Shplonk = ::proof_system::honk::pcs::shplonk::ShplonkVerifier_; + using KZG = ::proof_system::honk::pcs::kzg::KZG; // note: This can only be KZG using VerifierCommitments = typename Flavor::VerifierCommitments; using CommitmentLabels = typename Flavor::CommitmentLabels; using RelationParams = ::proof_system::RelationParameters; + using UnivariateClaim = ::proof_system::honk::pcs::OpeningClaim; RelationParams relation_parameters; @@ -155,8 +155,7 @@ std::array UltraRecursiveVerifier_(commitments.get_unshifted(), scalars_unshifted); + auto batched_commitment_unshifted = GroupElement::batch_mul(commitments.get_unshifted(), scalars_unshifted); info("Batch mul (unshifted): num gates = ", builder->get_num_gates() - prev_num_gates, @@ -166,7 +165,7 @@ std::array UltraRecursiveVerifier_get_num_gates(); auto batched_commitment_to_be_shifted = - GroupElement::template batch_mul(commitments.get_to_be_shifted(), scalars_to_be_shifted); + GroupElement::batch_mul(commitments.get_to_be_shifted(), scalars_to_be_shifted); info("Batch mul (to-be-shited): num gates = ", builder->get_num_gates() - prev_num_gates, @@ -178,11 +177,11 @@ std::array UltraRecursiveVerifier_get_num_gates(); + // Perform ECC op queue transcript aggregation protocol + if constexpr (IsGoblinFlavor) { + // Receive commitments [t_i^{shift}], [T_{i-1}], and [T_i] + std::array prev_agg_op_queue_commitments; + std::array shifted_op_wire_commitments; + std::array agg_op_queue_commitments; + for (size_t idx = 0; idx < Flavor::NUM_WIRES; ++idx) { + std::string suffix = std::to_string(idx + 1); + prev_agg_op_queue_commitments[idx] = + transcript.template receive_from_prover("PREV_AGG_OP_QUEUE_" + suffix); + shifted_op_wire_commitments[idx] = + transcript.template receive_from_prover("SHIFTED_OP_WIRE_" + suffix); + agg_op_queue_commitments[idx] = + transcript.template receive_from_prover("AGG_OP_QUEUE_" + suffix); + } + + // Receive claimed evaluations of t_i^{shift}, T_{i-1}, and T_i + FF kappa = transcript.get_challenge("kappa"); + std::array prev_agg_op_queue_evals; + std::array shifted_op_wire_evals; + std::array agg_op_queue_evals; + for (size_t idx = 0; idx < Flavor::NUM_WIRES; ++idx) { + std::string suffix = std::to_string(idx + 1); + prev_agg_op_queue_evals[idx] = + transcript.template receive_from_prover("prev_agg_op_queue_eval_" + suffix); + shifted_op_wire_evals[idx] = transcript.template receive_from_prover("op_wire_eval_" + suffix); + agg_op_queue_evals[idx] = transcript.template receive_from_prover("agg_op_queue_eval_" + suffix); + + ASSERT(agg_op_queue_evals[idx].get_value() == + prev_agg_op_queue_evals[idx].get_value() + shifted_op_wire_evals[idx].get_value()); + + // Check the identity T_i(\kappa) = T_{i-1}(\kappa) + t_i^{shift}(\kappa). + agg_op_queue_evals[idx].assert_equal(prev_agg_op_queue_evals[idx] + shifted_op_wire_evals[idx]); + } + + // Add corresponding univariate opening claims {(\kappa, p(\kappa), [p(X)]} + for (size_t idx = 0; idx < Flavor::NUM_WIRES; ++idx) { + univariate_opening_claims.emplace_back( + UnivariateClaim{ { kappa, prev_agg_op_queue_evals[idx] }, prev_agg_op_queue_commitments[idx] }); + } + for (size_t idx = 0; idx < Flavor::NUM_WIRES; ++idx) { + univariate_opening_claims.emplace_back( + UnivariateClaim{ { kappa, shifted_op_wire_evals[idx] }, shifted_op_wire_commitments[idx] }); + } + for (size_t idx = 0; idx < Flavor::NUM_WIRES; ++idx) { + univariate_opening_claims.emplace_back( + UnivariateClaim{ { kappa, agg_op_queue_evals[idx] }, agg_op_queue_commitments[idx] }); + } + } + // Produce a Shplonk claim: commitment [Q] - [Q_z], evaluation zero (at random challenge z) - auto shplonk_claim = Shplonk::reduce_verification(pcs_verification_key, gemini_claim, transcript); + auto shplonk_claim = Shplonk::reduce_verification(pcs_verification_key, univariate_opening_claims, transcript); info("Shplonk: num gates = ", builder->get_num_gates() - prev_num_gates, @@ -209,8 +258,9 @@ std::array UltraRecursiveVerifier_; -template class UltraRecursiveVerifier_; +template class UltraRecursiveVerifier_>; +template class UltraRecursiveVerifier_>; +template class UltraRecursiveVerifier_>; +template class UltraRecursiveVerifier_>; } // namespace proof_system::plonk::stdlib::recursion::honk diff --git a/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/verifier/ultra_recursive_verifier.hpp b/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/verifier/ultra_recursive_verifier.hpp index d20a9d161e38..298e60427cad 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/verifier/ultra_recursive_verifier.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/verifier/ultra_recursive_verifier.hpp @@ -1,5 +1,6 @@ #pragma once #include "barretenberg/honk/flavor/goblin_ultra.hpp" +#include "barretenberg/honk/flavor/goblin_ultra_recursive.hpp" #include "barretenberg/honk/flavor/ultra.hpp" #include "barretenberg/honk/flavor/ultra_grumpkin.hpp" #include "barretenberg/honk/flavor/ultra_recursive.hpp" @@ -8,7 +9,8 @@ #include "barretenberg/stdlib/recursion/honk/transcript/transcript.hpp" namespace proof_system::plonk::stdlib::recursion::honk { -template class UltraRecursiveVerifier_ { +template class UltraRecursiveVerifier_ { + public: using FF = typename Flavor::FF; using Commitment = typename Flavor::Commitment; using GroupElement = typename Flavor::GroupElement; @@ -17,7 +19,6 @@ template class UltraRecursiveVerifie using Builder = typename Flavor::CircuitBuilder; using PairingPoints = std::array; - public: explicit UltraRecursiveVerifier_(Builder* builder, std::shared_ptr verifier_key = nullptr); UltraRecursiveVerifier_(UltraRecursiveVerifier_&& other) = delete; UltraRecursiveVerifier_(const UltraRecursiveVerifier_& other) = delete; @@ -36,10 +37,12 @@ template class UltraRecursiveVerifie Transcript transcript; }; -extern template class UltraRecursiveVerifier_; -extern template class UltraRecursiveVerifier_; - -using UltraRecursiveVerifier = - UltraRecursiveVerifier_; +// Instance declarations for Ultra and Goblin-Ultra verifier circuits with both conventional Ultra and Goblin-Ultra +// arithmetization. +extern template class UltraRecursiveVerifier_>; +extern template class UltraRecursiveVerifier_>; +extern template class UltraRecursiveVerifier_>; +extern template class UltraRecursiveVerifier_< + proof_system::honk::flavor::GoblinUltraRecursive_>; } // namespace proof_system::plonk::stdlib::recursion::honk diff --git a/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/verifier/verifier.test.cpp b/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/verifier/verifier.test.cpp index ed46293cf95d..557194b433c1 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/verifier/verifier.test.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/verifier/verifier.test.cpp @@ -1,61 +1,52 @@ -#include "barretenberg/honk/flavor/ultra_recursive.hpp" -#include "barretenberg/honk/proof_system/ultra_verifier.hpp" - #include "barretenberg/common/test.hpp" -#include "barretenberg/ecc/curves/bn254/fq12.hpp" -#include "barretenberg/ecc/curves/bn254/pairing.hpp" #include "barretenberg/honk/composer/ultra_composer.hpp" -#include "barretenberg/plonk/proof_system/proving_key/serialize.hpp" +#include "barretenberg/honk/flavor/ultra_recursive.hpp" +#include "barretenberg/honk/proof_system/ultra_verifier.hpp" +#include "barretenberg/stdlib/commitment/pedersen/pedersen.hpp" #include "barretenberg/stdlib/hash/blake3s/blake3s.hpp" -#include "barretenberg/stdlib/hash/pedersen/pedersen.hpp" #include "barretenberg/stdlib/primitives/curves/bn254.hpp" #include "barretenberg/stdlib/recursion/honk/verifier/ultra_recursive_verifier.hpp" -#include "barretenberg/stdlib/recursion/verification_key/verification_key.hpp" -#include "barretenberg/transcript/transcript.hpp" namespace proof_system::plonk::stdlib::recursion::honk { /** - * @brief Test suite for Ultra Honk Recursive Verifier - * @details Construct and check a recursive verifier circuit for an UltraHonk proof using 1) the conventional Ultra - * arithmetization, or 2) a Goblin-style Ultra arithmetization + * @brief Test suite for recursive verification of conventional Ultra Honk proofs + * @details The recursive verification circuit is arithmetized in two different ways: 1) using the conventional Ultra + * arithmetization (UltraCircuitBuilder), or 2) a Goblin-style Ultra arithmetization (GoblinUltraCircuitBuilder). * - * @tparam UseGoblinFlag whether or not to use goblin-style arithmetization for group operations + * @tparam Builder */ -template class RecursiveVerifierTest : public testing::Test { - - static constexpr bool goblin_flag = UseGoblinFlag::value; +template class RecursiveVerifierTest : public testing::Test { - using InnerComposer = ::proof_system::honk::UltraComposer; + // Define types relevant for inner circuit + using Flavor = ::proof_system::honk::flavor::Ultra; + using InnerComposer = ::proof_system::honk::UltraComposer_; using InnerBuilder = typename InnerComposer::CircuitBuilder; - - using OuterBuilder = ::proof_system::UltraCircuitBuilder; - using NativeVerifier = ::proof_system::honk::UltraVerifier_<::proof_system::honk::flavor::Ultra>; - // Arithmetization of group operations in recursive verifier circuit (goblin or not) is determined by goblin_flag - using RecursiveVerifier = UltraRecursiveVerifier_<::proof_system::honk::flavor::UltraRecursive, goblin_flag>; - using VerificationKey = ::proof_system::honk::flavor::UltraRecursive::VerificationKey; + using InnerCurve = bn254; - using inner_curve = bn254; - using inner_scalar_field_ct = inner_curve::ScalarField; - using inner_ground_field_ct = inner_curve::BaseField; - using public_witness_ct = inner_curve::public_witness_ct; - using witness_ct = inner_curve::witness_ct; - using byte_array_ct = inner_curve::byte_array_ct; - - using inner_scalar_field = typename inner_curve::ScalarFieldNative; + // Types for recursive verifier circuit + using RecursiveFlavor = ::proof_system::honk::flavor::UltraRecursive_; + using RecursiveVerifier = UltraRecursiveVerifier_; + using OuterBuilder = BuilderType; + using VerificationKey = typename RecursiveVerifier::VerificationKey; /** - * @brief Create an inner circuit, the proof of which will be recursively verified + * @brief Create a non-trivial arbitrary inner circuit, the proof of which will be recursively verified * * @param builder * @param public_inputs * @param log_num_gates */ - static void create_inner_circuit(InnerBuilder& builder, - const std::vector& public_inputs, - size_t log_num_gates = 10) + static void create_inner_circuit(InnerBuilder& builder, size_t log_num_gates = 10) { + using fr_ct = InnerCurve::ScalarField; + using fq_ct = InnerCurve::BaseField; + using public_witness_ct = InnerCurve::public_witness_ct; + using witness_ct = InnerCurve::witness_ct; + using byte_array_ct = InnerCurve::byte_array_ct; + using fr = typename InnerCurve::ScalarFieldNative; + // Create 2^log_n many add gates based on input log num gates const size_t num_gates = 1 << log_num_gates; for (size_t i = 0; i < num_gates; ++i) { @@ -72,10 +63,10 @@ template class RecursiveVerifierTest : public testing:: builder.create_big_add_gate({ a_idx, b_idx, c_idx, d_idx, fr(1), fr(1), fr(1), fr(-1), fr(0) }); } - // Create some additional "circuity" gates as an example - inner_scalar_field_ct a(public_witness_ct(&builder, public_inputs[0])); - inner_scalar_field_ct b(public_witness_ct(&builder, public_inputs[1])); - inner_scalar_field_ct c(public_witness_ct(&builder, public_inputs[2])); + // Define some additional non-trivial but arbitrary circuit logic + fr_ct a(public_witness_ct(&builder, fr::random_element())); + fr_ct b(public_witness_ct(&builder, fr::random_element())); + fr_ct c(public_witness_ct(&builder, fr::random_element())); for (size_t i = 0; i < 32; ++i) { a = (a * b) + b + a; @@ -85,70 +76,16 @@ template class RecursiveVerifierTest : public testing:: byte_array_ct to_hash(&builder, "nonsense test data"); blake3s(to_hash); - inner_scalar_field bigfield_data = fr::random_element(); - inner_scalar_field bigfield_data_a{ bigfield_data.data[0], bigfield_data.data[1], 0, 0 }; - inner_scalar_field bigfield_data_b{ bigfield_data.data[2], bigfield_data.data[3], 0, 0 }; + fr bigfield_data = fr::random_element(); + fr bigfield_data_a{ bigfield_data.data[0], bigfield_data.data[1], 0, 0 }; + fr bigfield_data_b{ bigfield_data.data[2], bigfield_data.data[3], 0, 0 }; - inner_ground_field_ct big_a(inner_scalar_field_ct(witness_ct(&builder, bigfield_data_a.to_montgomery_form())), - inner_scalar_field_ct(witness_ct(&builder, 0))); - inner_ground_field_ct big_b(inner_scalar_field_ct(witness_ct(&builder, bigfield_data_b.to_montgomery_form())), - inner_scalar_field_ct(witness_ct(&builder, 0))); + fq_ct big_a(fr_ct(witness_ct(&builder, bigfield_data_a.to_montgomery_form())), fr_ct(witness_ct(&builder, 0))); + fq_ct big_b(fr_ct(witness_ct(&builder, bigfield_data_b.to_montgomery_form())), fr_ct(witness_ct(&builder, 0))); big_a* big_b; }; - /** - * @brief Create a recursive verifier circuit and perform some native consistency checks - * @details Given an arbitrary inner circuit, construct a proof then consturct a recursive verifier circuit for that - * proof using the provided outer circuit builder. - * @note: The output of recursive verification is the two points which could be used in a pairing to do final - * verification. As a consistency check, we check that the outcome of performing this pairing (natively, no - * constraints) matches the outcome of running the full native verifier. - * - * @param inner_circuit Builder of the circuit for which a proof is recursively verified - * @param outer_builder Builder for the recursive verifier circuit - */ - static void create_outer_circuit(InnerBuilder& inner_circuit, OuterBuilder& outer_builder) - { - // Create proof of inner circuit - InnerComposer inner_composer; - auto instance = inner_composer.create_instance(inner_circuit); - auto prover = inner_composer.create_prover(instance); - auto proof_to_recursively_verify = prover.construct_proof(); - - info("Inner circuit size = ", instance->proving_key->circuit_size); - - // Compute native verification key - const auto native_verification_key = instance->compute_verification_key(); - - // Instantiate the recursive verification key from the native verification key - auto verification_key = std::make_shared(&outer_builder, native_verification_key); - - // Instantiate the recursive verifier and construct the recusive verification circuit - RecursiveVerifier verifier(&outer_builder, verification_key); - auto pairing_points = verifier.verify_proof(proof_to_recursively_verify); - - // For testing purposes only, perform native verification and compare the result - auto native_verifier = inner_composer.create_verifier(instance); - auto native_result = native_verifier.verify_proof(proof_to_recursively_verify); - - // Extract the pairing points from the recursive verifier output and perform the pairing natively. The result - // should match that of native verification. - auto lhs = pairing_points[0].get_value(); - auto rhs = pairing_points[1].get_value(); - auto recursive_result = native_verifier.pcs_verification_key->pairing_check(lhs, rhs); - EXPECT_EQ(recursive_result, native_result); - - // Confirm that the manifests produced by the recursive and native verifiers agree - auto recursive_manifest = verifier.transcript.get_manifest(); - auto native_manifest = native_verifier.transcript.get_manifest(); - // recursive_manifest.print(); - // native_manifest.print(); - for (size_t i = 0; i < recursive_manifest.size(); ++i) { - EXPECT_EQ(recursive_manifest[i], native_manifest[i]); - } - }; - public: static void SetUpTestSuite() { barretenberg::srs::init_crs_factory("../srs_db/ignition"); } @@ -159,11 +96,8 @@ template class RecursiveVerifierTest : public testing:: static void test_inner_circuit() { InnerBuilder builder; - std::vector inputs{ inner_scalar_field::random_element(), - inner_scalar_field::random_element(), - inner_scalar_field::random_element() }; - create_inner_circuit(builder, inputs); + create_inner_circuit(builder); bool result = builder.check_circuit(); EXPECT_EQ(result, true); @@ -179,12 +113,8 @@ template class RecursiveVerifierTest : public testing:: InnerBuilder inner_circuit; OuterBuilder outer_circuit; - std::vector inner_public_inputs{ inner_scalar_field::random_element(), - inner_scalar_field::random_element(), - inner_scalar_field::random_element() }; - // Create an arbitrary inner circuit - create_inner_circuit(inner_circuit, inner_public_inputs); + create_inner_circuit(inner_circuit); // Compute native verification key InnerComposer inner_composer; @@ -209,31 +139,53 @@ template class RecursiveVerifierTest : public testing:: * @brief Construct a recursive verification circuit for the proof of an inner circuit then call check_circuit on it * */ - static void test_recursive_proof_composition() + static void test_recursive_verification() { + // Create an arbitrary inner circuit InnerBuilder inner_circuit; - OuterBuilder outer_circuit; - - std::vector inner_public_inputs{ inner_scalar_field::random_element(), - inner_scalar_field::random_element(), - inner_scalar_field::random_element() }; + create_inner_circuit(inner_circuit); - // Create an arbitrary inner circuit - create_inner_circuit(inner_circuit, inner_public_inputs); + // Generate a proof over the inner circuit + InnerComposer inner_composer; + auto instance = inner_composer.create_instance(inner_circuit); + auto inner_prover = inner_composer.create_prover(instance); + auto inner_proof = inner_prover.construct_proof(); + const auto native_verification_key = instance->compute_verification_key(); // Create a recursive verification circuit for the proof of the inner circuit - create_outer_circuit(inner_circuit, outer_circuit); + OuterBuilder outer_circuit; + auto verification_key = std::make_shared(&outer_circuit, native_verification_key); + RecursiveVerifier verifier(&outer_circuit, verification_key); + auto pairing_points = verifier.verify_proof(inner_proof); + // Check the recursive verifier circuit EXPECT_EQ(outer_circuit.failed(), false) << outer_circuit.err(); EXPECT_TRUE(outer_circuit.check_circuit()); + + // Additional check 1: Perform native verification then perform the pairing on the outputs of the recursive + // verifier and check that the result agrees. + auto native_verifier = inner_composer.create_verifier(instance); + auto native_result = native_verifier.verify_proof(inner_proof); + auto recursive_result = native_verifier.pcs_verification_key->pairing_check(pairing_points[0].get_value(), + pairing_points[1].get_value()); + EXPECT_EQ(recursive_result, native_result); + + // Additional check 2: Ensure that the underlying native and recursive verification algorithms agree by ensuring + // the manifests produced by each agree. + auto recursive_manifest = verifier.transcript.get_manifest(); + auto native_manifest = native_verifier.transcript.get_manifest(); + // recursive_manifest.print(); + // native_manifest.print(); + for (size_t i = 0; i < recursive_manifest.size(); ++i) { + EXPECT_EQ(recursive_manifest[i], native_manifest[i]); + } } }; -// Run the recursive verifier tests twice, once without using a goblin style arithmetization of group operations and -// once with. -using UseGoblinFlag = testing::Types; +// Run the recursive verifier tests with conventional Ultra builder and Goblin builder +using BuilderTypes = testing::Types; -TYPED_TEST_SUITE(RecursiveVerifierTest, UseGoblinFlag); +TYPED_TEST_SUITE(RecursiveVerifierTest, BuilderTypes); HEAVY_TYPED_TEST(RecursiveVerifierTest, InnerCircuit) { @@ -245,9 +197,9 @@ HEAVY_TYPED_TEST(RecursiveVerifierTest, RecursiveVerificationKey) TestFixture::test_recursive_verification_key_creation(); } -HEAVY_TYPED_TEST(RecursiveVerifierTest, RecursiveProofComposition) +HEAVY_TYPED_TEST(RecursiveVerifierTest, SingleRecursiveVerification) { - TestFixture::test_recursive_proof_composition(); + TestFixture::test_recursive_verification(); }; } // namespace proof_system::plonk::stdlib::recursion::honk \ No newline at end of file