diff --git a/cpp/src/barretenberg/honk/composer/standard_honk_composer.test.cpp b/cpp/src/barretenberg/honk/composer/standard_honk_composer.test.cpp index 78a8c930ad..a13e0df167 100644 --- a/cpp/src/barretenberg/honk/composer/standard_honk_composer.test.cpp +++ b/cpp/src/barretenberg/honk/composer/standard_honk_composer.test.cpp @@ -1,13 +1,12 @@ #include "standard_honk_composer.hpp" -#include "barretenberg/honk/sumcheck/relations/relation.hpp" +#include "barretenberg/honk/sumcheck/relations/relation_parameters.hpp" #include "barretenberg/numeric/uint256/uint256.hpp" #include #include #include #include "barretenberg/honk/proof_system/prover.hpp" #include "barretenberg/honk/sumcheck/sumcheck_round.hpp" -#include "barretenberg/honk/sumcheck/relations/grand_product_computation_relation.hpp" -#include "barretenberg/honk/sumcheck/relations/grand_product_initialization_relation.hpp" +#include "barretenberg/honk/sumcheck/relations/permutation_relation.hpp" #include "barretenberg/polynomials/polynomial.hpp" #pragma GCC diagnostic ignored "-Wunused-variable" diff --git a/cpp/src/barretenberg/honk/composer/ultra_honk_composer.test.cpp b/cpp/src/barretenberg/honk/composer/ultra_honk_composer.test.cpp index 09b3373e7f..2eb23f820a 100644 --- a/cpp/src/barretenberg/honk/composer/ultra_honk_composer.test.cpp +++ b/cpp/src/barretenberg/honk/composer/ultra_honk_composer.test.cpp @@ -1,14 +1,13 @@ #include "ultra_honk_composer.hpp" #include "barretenberg/common/log.hpp" #include "barretenberg/honk/proof_system/ultra_prover.hpp" -#include "barretenberg/honk/sumcheck/relations/relation.hpp" +#include "barretenberg/honk/sumcheck/relations/relation_parameters.hpp" #include "barretenberg/numeric/uint256/uint256.hpp" #include #include #include "barretenberg/honk/proof_system/prover.hpp" #include "barretenberg/honk/sumcheck/sumcheck_round.hpp" -#include "barretenberg/honk/sumcheck/relations/grand_product_computation_relation.hpp" -#include "barretenberg/honk/sumcheck/relations/grand_product_initialization_relation.hpp" +#include "barretenberg/honk/sumcheck/relations/permutation_relation.hpp" #include "barretenberg/honk/utils/grand_product_delta.hpp" #include "barretenberg/proof_system/plookup_tables/types.hpp" @@ -649,7 +648,6 @@ TEST(UltraHonkComposer, non_native_field_multiplication) fq a = fq::random_element(); fq b = fq::random_element(); - uint256_t modulus = fq::modulus; uint1024_t a_big = uint512_t(uint256_t(a)); diff --git a/cpp/src/barretenberg/honk/flavor/standard.hpp b/cpp/src/barretenberg/honk/flavor/standard.hpp index 1557fb8814..29fed767c0 100644 --- a/cpp/src/barretenberg/honk/flavor/standard.hpp +++ b/cpp/src/barretenberg/honk/flavor/standard.hpp @@ -10,8 +10,7 @@ #include "barretenberg/honk/sumcheck/polynomials/univariate.hpp" #include "barretenberg/ecc/curves/bn254/g1.hpp" #include "barretenberg/honk/sumcheck/relations/arithmetic_relation.hpp" -#include "barretenberg/honk/sumcheck/relations/grand_product_computation_relation.hpp" -#include "barretenberg/honk/sumcheck/relations/grand_product_initialization_relation.hpp" +#include "barretenberg/honk/sumcheck/relations/permutation_relation.hpp" #include "barretenberg/honk/transcript/transcript.hpp" #include "barretenberg/plonk/proof_system/proving_key/proving_key.hpp" #include "barretenberg/polynomials/evaluation_domain.hpp" @@ -53,17 +52,20 @@ class Standard { static constexpr size_t NUM_WITNESS_ENTITIES = 4; // define the tuple of Relations that comprise the Sumcheck relation - using Relations = std::tuple, - sumcheck::GrandProductComputationRelation, - sumcheck::GrandProductInitializationRelation>; + using Relations = std::tuple, sumcheck::PermutationRelation>; static constexpr size_t MAX_RELATION_LENGTH = get_max_relation_length(); static constexpr size_t NUM_RELATIONS = std::tuple_size::value; - // define the container for storing the univariate contribution from each relation in Sumcheck - using UnivariateTuple = decltype(create_univariate_tuple()); + // Instantiate the BarycentricData needed to extend each Relation Univariate + static_assert(instantiate_barycentric_utils()); + + // define the containers for storing the contributions from each relation in Sumcheck + using RelationUnivariates = decltype(create_relation_univariates_container()); + using RelationValues = decltype(create_relation_values_container()); + // define utilities to extend univarates from RELATION_LENGTH to MAX_RELATION_LENGTH for each Relation - using BarycentricUtils = decltype(create_barycentric_utils()); + // using BarycentricUtils = decltype(create_barycentric_utils()); private: /** diff --git a/cpp/src/barretenberg/honk/flavor/ultra.hpp b/cpp/src/barretenberg/honk/flavor/ultra.hpp index a3d016bd60..b1670666db 100644 --- a/cpp/src/barretenberg/honk/flavor/ultra.hpp +++ b/cpp/src/barretenberg/honk/flavor/ultra.hpp @@ -17,10 +17,8 @@ #include "barretenberg/srs/reference_string/reference_string.hpp" #include "barretenberg/proof_system/flavor/flavor.hpp" #include "barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation.hpp" -#include "barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation_secondary.hpp" -#include "barretenberg/honk/sumcheck/relations/grand_product_computation_relation.hpp" -#include "barretenberg/honk/sumcheck/relations/grand_product_initialization_relation.hpp" -#include "barretenberg/honk/sumcheck/relations/lookup_grand_product_relation.hpp" +#include "barretenberg/honk/sumcheck/relations/permutation_relation.hpp" +#include "barretenberg/honk/sumcheck/relations/lookup_relation.hpp" #include "barretenberg/honk/sumcheck/relations/gen_perm_sort_relation.hpp" #include "barretenberg/honk/sumcheck/relations/elliptic_relation.hpp" #include "barretenberg/honk/sumcheck/relations/auxiliary_relation.hpp" @@ -52,11 +50,8 @@ class Ultra { // define the tuple of Relations that comprise the Sumcheck relation using Relations = std::tuple, - sumcheck::UltraArithmeticRelationSecondary, - sumcheck::UltraGrandProductComputationRelation, - sumcheck::UltraGrandProductInitializationRelation, - sumcheck::LookupGrandProductComputationRelation, - sumcheck::LookupGrandProductInitializationRelation, + sumcheck::UltraPermutationRelation, + sumcheck::LookupRelation, sumcheck::GenPermSortRelation, sumcheck::EllipticRelation, sumcheck::AuxiliaryRelation>; @@ -64,10 +59,12 @@ class Ultra { static constexpr size_t MAX_RELATION_LENGTH = get_max_relation_length(); static constexpr size_t NUM_RELATIONS = std::tuple_size::value; + // Instantiate the BarycentricData needed to extend each Relation Univariate + static_assert(instantiate_barycentric_utils()); + // define the container for storing the univariate contribution from each relation in Sumcheck - using UnivariateTuple = decltype(create_univariate_tuple()); - // define utilities to extend univarates from RELATION_LENGTH to MAX_RELATION_LENGTH for each Relation - using BarycentricUtils = decltype(create_barycentric_utils()); + using RelationUnivariates = decltype(create_relation_univariates_container()); + using RelationValues = decltype(create_relation_values_container()); private: template diff --git a/cpp/src/barretenberg/honk/proof_system/ultra_prover.hpp b/cpp/src/barretenberg/honk/proof_system/ultra_prover.hpp index bcf665711c..5b42bbd150 100644 --- a/cpp/src/barretenberg/honk/proof_system/ultra_prover.hpp +++ b/cpp/src/barretenberg/honk/proof_system/ultra_prover.hpp @@ -6,7 +6,7 @@ #include "barretenberg/honk/pcs/kzg/kzg.hpp" #include "barretenberg/honk/transcript/transcript.hpp" #include "barretenberg/honk/flavor/ultra.hpp" -#include "barretenberg/honk/sumcheck/relations/relation.hpp" +#include "barretenberg/honk/sumcheck/relations/relation_parameters.hpp" #include "barretenberg/honk/sumcheck/sumcheck_output.hpp" namespace proof_system::honk { diff --git a/cpp/src/barretenberg/honk/sumcheck/relations/arithmetic_relation.hpp b/cpp/src/barretenberg/honk/sumcheck/relations/arithmetic_relation.hpp index e7d095119c..1f0349fae5 100644 --- a/cpp/src/barretenberg/honk/sumcheck/relations/arithmetic_relation.hpp +++ b/cpp/src/barretenberg/honk/sumcheck/relations/arithmetic_relation.hpp @@ -3,7 +3,7 @@ #include #include "../polynomials/univariate.hpp" -#include "relation.hpp" +#include "relation_parameters.hpp" namespace proof_system::honk::sumcheck { @@ -12,6 +12,12 @@ template class ArithmeticRelation { // 1 + polynomial degree of this relation static constexpr size_t RELATION_LENGTH = 4; + static constexpr size_t NUM_CONSTRAINTS = 1; + static constexpr std::array CONSTRAINT_LENGTH = { 4 }; + + using RelationUnivariates = std::tuple>; + using RelationValues = std::array; + /** * @brief Expression for the StandardArithmetic gate. * @details The relation is defined as C(extended_edges(X)...) = @@ -22,7 +28,7 @@ template class ArithmeticRelation { * @param parameters contains beta, gamma, and public_input_delta, .... * @param scaling_factor optional term to scale the evaluation before adding to evals. */ - void add_edge_contribution(Univariate& evals, + void add_edge_contribution(RelationUnivariates& evals, const auto& extended_edges, const RelationParameters&, const FF& scaling_factor) const @@ -44,10 +50,17 @@ template class ArithmeticRelation { tmp += q_o * w_o; tmp += q_c; tmp *= scaling_factor; - evals += tmp; + std::get<0>(evals) += tmp; }; - void add_full_relation_value_contribution(FF& full_honk_relation_value, + /** + * @brief Add the result of each identity in this relation evaluated at the multivariate evaluations produced by the + * Sumcheck Prover. + * + * @param full_honk_relation_value + * @param purported_evaluations + */ + void add_full_relation_value_contribution(RelationValues& full_honk_relation_value, const auto& purported_evaluations, const RelationParameters&) const { @@ -60,10 +73,10 @@ template class ArithmeticRelation { auto q_o = purported_evaluations.q_o; auto q_c = purported_evaluations.q_c; - full_honk_relation_value += w_l * (q_m * w_r + q_l); - full_honk_relation_value += q_r * w_r; - full_honk_relation_value += q_o * w_o; - full_honk_relation_value += q_c; + std::get<0>(full_honk_relation_value) += w_l * (q_m * w_r + q_l); + std::get<0>(full_honk_relation_value) += q_r * w_r; + std::get<0>(full_honk_relation_value) += q_o * w_o; + std::get<0>(full_honk_relation_value) += q_c; }; }; } // namespace proof_system::honk::sumcheck diff --git a/cpp/src/barretenberg/honk/sumcheck/relations/auxiliary_relation.hpp b/cpp/src/barretenberg/honk/sumcheck/relations/auxiliary_relation.hpp index c8b7f288a2..890e16326b 100644 --- a/cpp/src/barretenberg/honk/sumcheck/relations/auxiliary_relation.hpp +++ b/cpp/src/barretenberg/honk/sumcheck/relations/auxiliary_relation.hpp @@ -4,26 +4,61 @@ #include "../polynomials/univariate.hpp" #include "barretenberg/numeric/uint256/uint256.hpp" -#include "relation.hpp" +#include "relation_parameters.hpp" namespace proof_system::honk::sumcheck { template class AuxiliaryRelation { public: // 1 + polynomial degree of this relation - static constexpr size_t RELATION_LENGTH = 6; // degree() = WORKTODO + static constexpr size_t RELATION_LENGTH = 6; + + static constexpr size_t NUM_CONSTRAINTS = 6; + static constexpr std::array CONSTRAINT_LENGTH = { 6, 6, 6, 6, 6, 6 }; // Each has degree 5 + + using RelationUnivariates = std::tuple, + Univariate, + Univariate, + Univariate, + Univariate, + Univariate>; + using RelationValues = std::array; /** * @brief Expression for the generalized permutation sort gate. - * @details The relation is defined as C(extended_edges(X)...) = - * //WORKTODO + * @details The following explanation is reproduced from the Plonk analog 'plookup_auxiliary_widget': + * Adds contributions for identities associated with several custom gates: + * * RAM/ROM read-write consistency check + * * RAM timestamp difference consistency check + * * RAM/ROM index difference consistency check + * * Bigfield product evaluation (3 in total) + * * Bigfield limb accumulation (2 in total) + * + * Multiple selectors are used to 'switch' aux gates on/off according to the following pattern: + * + * | gate type | q_aux | q_1 | q_2 | q_3 | q_4 | q_m | q_c | q_arith | + * | ---------------------------- | ----- | --- | --- | --- | --- | --- | --- | ------ | + * | Bigfield Limb Accumulation 1 | 1 | 0 | 0 | 1 | 1 | 0 | --- | 0 | + * | Bigfield Limb Accumulation 2 | 1 | 0 | 0 | 1 | 0 | 1 | --- | 0 | + * | Bigfield Product 1 | 1 | 0 | 1 | 1 | 0 | 0 | --- | 0 | + * | Bigfield Product 2 | 1 | 0 | 1 | 0 | 1 | 0 | --- | 0 | + * | Bigfield Product 3 | 1 | 0 | 1 | 0 | 0 | 1 | --- | 0 | + * | RAM/ROM access gate | 1 | 1 | 0 | 0 | 0 | 1 | --- | 0 | + * | RAM timestamp check | 1 | 1 | 0 | 0 | 1 | 0 | --- | 0 | + * | ROM consistency check | 1 | 1 | 1 | 0 | 0 | 0 | --- | 0 | + * | RAM consistency check | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | + * + * N.B. The RAM consistency check identity is degree 3. To keep the overall quotient degree at <=5, only 2 selectors + * can be used to select it. + * + * N.B.2 The q_c selector is used to store circuit-specific values in the RAM/ROM access gate * * @param evals transformed to `evals + C(extended_edges(X)...)*scaling_factor` * @param extended_edges an std::array containing the fully extended Univariate edges. * @param parameters contains beta, gamma, and public_input_delta, .... * @param scaling_factor optional term to scale the evaluation before adding to evals. */ - void add_edge_contribution(Univariate& evals, + void add_edge_contribution(RelationUnivariates& evals, const auto& extended_edges, const RelationParameters& relation_parameters, const FF& scaling_factor) const @@ -32,7 +67,6 @@ template class AuxiliaryRelation { // See https://hackmd.io/xGLuj6biSsCjzQnYN-pEiA?both const auto& eta = relation_parameters.eta; - const auto fake_alpha = FF(1); auto w_1 = UnivariateView(extended_edges.w_l); auto w_2 = UnivariateView(extended_edges.w_r); @@ -177,11 +211,9 @@ template class AuxiliaryRelation { auto adjacent_values_match_if_adjacent_indices_match = (index_delta * FF(-1) + FF(1)) * record_delta; - auto ROM_consistency_check_identity = adjacent_values_match_if_adjacent_indices_match; - ROM_consistency_check_identity *= fake_alpha; - ROM_consistency_check_identity += index_is_monotonically_increasing; - ROM_consistency_check_identity *= fake_alpha; - ROM_consistency_check_identity += memory_record_check; + std::get<1>(evals) += adjacent_values_match_if_adjacent_indices_match * (q_1 * q_2) * (q_aux * scaling_factor); + std::get<2>(evals) += index_is_monotonically_increasing * (q_1 * q_2) * (q_aux * scaling_factor); + auto ROM_consistency_check_identity = memory_record_check * (q_1 * q_2); /** * RAM Consistency Check @@ -224,14 +256,11 @@ template class AuxiliaryRelation { auto next_gate_access_type_is_boolean = next_gate_access_type * next_gate_access_type - next_gate_access_type; // Putting it all together... - auto RAM_consistency_check_identity = - adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation; - RAM_consistency_check_identity *= fake_alpha; - RAM_consistency_check_identity += index_is_monotonically_increasing; - RAM_consistency_check_identity *= fake_alpha; - RAM_consistency_check_identity += next_gate_access_type_is_boolean; - RAM_consistency_check_identity *= fake_alpha; - RAM_consistency_check_identity += access_check; + std::get<3>(evals) += adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation * + (q_arith) * (q_aux * scaling_factor); + std::get<4>(evals) += index_is_monotonically_increasing * (q_arith) * (q_aux * scaling_factor); + std::get<5>(evals) += next_gate_access_type_is_boolean * (q_arith) * (q_aux * scaling_factor); + auto RAM_consistency_check_identity = access_check * (q_arith); /** * RAM Timestamp Consistency Check @@ -251,25 +280,28 @@ template class AuxiliaryRelation { * The complete RAM/ROM memory identity * */ - auto memory_identity = ROM_consistency_check_identity * q_2; - memory_identity += RAM_timestamp_check_identity * q_4; - memory_identity += memory_record_check * q_m; - memory_identity *= q_1; - memory_identity += (RAM_consistency_check_identity * q_arith); + auto memory_identity = ROM_consistency_check_identity; + memory_identity += RAM_timestamp_check_identity * (q_4 * q_1); + memory_identity += memory_record_check * (q_m * q_1); + memory_identity += RAM_consistency_check_identity; auto auxiliary_identity = memory_identity + non_native_field_identity + limb_accumulator_identity; - auxiliary_identity *= q_aux; - auxiliary_identity *= scaling_factor; - - evals += auxiliary_identity; + auxiliary_identity *= (q_aux * scaling_factor); + std::get<0>(evals) += auxiliary_identity; }; - void add_full_relation_value_contribution(FF& full_honk_relation_value, + /** + * @brief Add the result of each identity in this relation evaluated at the multivariate evaluations produced by the + * Sumcheck Prover. + * + * @param full_honk_relation_value + * @param purported_evaluations + */ + void add_full_relation_value_contribution(RelationValues& full_honk_relation_value, const auto& purported_evaluations, const RelationParameters& relation_parameters) const { const auto& eta = relation_parameters.eta; - const auto fake_alpha = FF(1); auto w_1 = purported_evaluations.w_l; auto w_2 = purported_evaluations.w_r; @@ -417,11 +449,9 @@ template class AuxiliaryRelation { auto adjacent_values_match_if_adjacent_indices_match = (FF(1) - index_delta) * record_delta; - auto ROM_consistency_check_identity = adjacent_values_match_if_adjacent_indices_match; - ROM_consistency_check_identity *= fake_alpha; - ROM_consistency_check_identity += index_is_monotonically_increasing; - ROM_consistency_check_identity *= fake_alpha; - ROM_consistency_check_identity += memory_record_check; + std::get<1>(full_honk_relation_value) += adjacent_values_match_if_adjacent_indices_match * (q_1 * q_2) * q_aux; + std::get<2>(full_honk_relation_value) += index_is_monotonically_increasing * (q_1 * q_2) * q_aux; + auto ROM_consistency_check_identity = memory_record_check * (q_1 * q_2); /** * RAM Consistency Check @@ -465,14 +495,11 @@ template class AuxiliaryRelation { auto next_gate_access_type_is_boolean = next_gate_access_type.sqr() - next_gate_access_type; // Putting it all together... - auto RAM_consistency_check_identity = - adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation; - RAM_consistency_check_identity *= fake_alpha; - RAM_consistency_check_identity += index_is_monotonically_increasing; - RAM_consistency_check_identity *= fake_alpha; - RAM_consistency_check_identity += next_gate_access_type_is_boolean; - RAM_consistency_check_identity *= fake_alpha; - RAM_consistency_check_identity += access_check; + std::get<3>(full_honk_relation_value) += + adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation * (q_arith)*q_aux; + std::get<4>(full_honk_relation_value) += index_is_monotonically_increasing * (q_arith)*q_aux; + std::get<5>(full_honk_relation_value) += next_gate_access_type_is_boolean * (q_arith)*q_aux; + auto RAM_consistency_check_identity = access_check * (q_arith); /** * RAM Timestamp Consistency Check @@ -492,17 +519,15 @@ template class AuxiliaryRelation { * The complete RAM/ROM memory identity * */ - auto memory_identity = ROM_consistency_check_identity * q_2; - memory_identity += RAM_timestamp_check_identity * q_4; - memory_identity += memory_record_check * q_m; - memory_identity *= q_1; - memory_identity += (RAM_consistency_check_identity * q_arith); + auto memory_identity = ROM_consistency_check_identity; + memory_identity += RAM_timestamp_check_identity * q_1 * q_4; + memory_identity += memory_record_check * q_1 * q_m; + memory_identity += RAM_consistency_check_identity; // auto auxiliary_identity = limb_accumulator_identity; auto auxiliary_identity = memory_identity + non_native_field_identity + limb_accumulator_identity; auxiliary_identity *= q_aux; - - full_honk_relation_value += auxiliary_identity; + std::get<0>(full_honk_relation_value) += auxiliary_identity; }; }; } // namespace proof_system::honk::sumcheck diff --git a/cpp/src/barretenberg/honk/sumcheck/relations/elliptic_relation.hpp b/cpp/src/barretenberg/honk/sumcheck/relations/elliptic_relation.hpp index 9063a43297..f05cacbf5b 100644 --- a/cpp/src/barretenberg/honk/sumcheck/relations/elliptic_relation.hpp +++ b/cpp/src/barretenberg/honk/sumcheck/relations/elliptic_relation.hpp @@ -3,14 +3,20 @@ #include #include "../polynomials/univariate.hpp" -#include "relation.hpp" +#include "relation_parameters.hpp" namespace proof_system::honk::sumcheck { template class EllipticRelation { public: // 1 + polynomial degree of this relation - static constexpr size_t RELATION_LENGTH = 6; // degree(q_arith^2 * q_m * w_r * w_l) = 5 + static constexpr size_t RELATION_LENGTH = 5; // degree(q_elliptic * q_beta * x^3) = 5 + + static constexpr size_t NUM_CONSTRAINTS = 2; + static constexpr std::array CONSTRAINT_LENGTH = { 6, 5 }; + + using RelationUnivariates = std::tuple, Univariate>; + using RelationValues = std::array; /** * @brief Expression for the Ultra Arithmetic gate. @@ -22,64 +28,83 @@ template class EllipticRelation { * @param parameters contains beta, gamma, and public_input_delta, .... * @param scaling_factor optional term to scale the evaluation before adding to evals. */ - void add_edge_contribution(Univariate& evals, + void add_edge_contribution(RelationUnivariates& evals, const auto& extended_edges, const RelationParameters&, - const FF& scaling_factor) const - { + const FF& scaling_factor) const { // OPTIMIZATION?: Karatsuba in general, at least for some degrees? // See https://hackmd.io/xGLuj6biSsCjzQnYN-pEiA?both - - auto x_1 = UnivariateView(extended_edges.w_r); - auto y_1 = UnivariateView(extended_edges.w_o); - - auto x_2 = UnivariateView(extended_edges.w_l_shift); - auto y_2 = UnivariateView(extended_edges.w_4_shift); - auto x_3 = UnivariateView(extended_edges.w_r_shift); - auto y_3 = UnivariateView(extended_edges.w_o_shift); - - auto q_sign = UnivariateView(extended_edges.q_l); - auto q_beta = UnivariateView(extended_edges.q_o); - auto q_beta_sqr = UnivariateView(extended_edges.q_4); - auto q_elliptic = UnivariateView(extended_edges.q_elliptic); - - static const FF fake_alpha_1 = FF(1); - static const FF fake_alpha_2 = fake_alpha_1 * fake_alpha_1; - - auto beta_term = x_2 * x_1 * (x_3 + x_3 + x_1) * FF(-1); // -x_1 * x_2 * (2 * x_3 + x_1) - auto beta_sqr_term = x_2 * x_2; // x_2^2 - auto leftovers = beta_sqr_term; // x_2^2 - beta_sqr_term *= (x_3 - x_1); // x_2^2 * (x_3 - x_1) - auto sign_term = y_2 * y_1; // y_1 * y_2 - sign_term += sign_term; // 2 * y_1 * y_2 - beta_term *= q_beta; // -β * x_1 * x_2 * (2 * x_3 + x_1) - beta_sqr_term *= q_beta_sqr; // β^2 * x_2^2 * (x_3 - x_1) - sign_term *= q_sign; // 2 * y_1 * y_2 * sign - leftovers *= x_2; // x_2^3 - leftovers += x_1 * x_1 * (x_3 + x_1); // x_2^3 + x_1 * (x_3 + x_1) - leftovers -= (y_2 * y_2 + y_1 * y_1); // x_2^3 + x_1 * (x_3 + x_1) - y_2^2 - y_1^2 - - // Can be found in class description - auto x_identity = beta_term + beta_sqr_term + sign_term + leftovers; - x_identity *= fake_alpha_1; - - beta_term = x_2 * (y_3 + y_1) * q_beta; // β * x_2 * (y_3 + y_1) - sign_term = y_2 * (x_1 - x_3) * q_sign * FF(-1); // - signt * y_2 * (x_1 - x_3) - // TODO: remove extra additions if we decide to stay with this implementation - leftovers = x_1 * (y_3 + y_1) * FF(-1) + y_1 * (x_1 - x_3); // -x_1 * y_3 - x_1 * y_1 + y_1 * x_1 - y_1 * x_3 - - auto y_identity = beta_term + sign_term + leftovers; - y_identity *= fake_alpha_2; - - auto tmp = x_identity + y_identity; - tmp *= q_elliptic; - - tmp *= scaling_factor; - - evals += tmp; + // TODO(luke): Formatter doesnt properly handle explicit scoping below so turning off. Whats up? + // clang-format off + // Contribution (1) + { + static constexpr size_t LENGTH = CONSTRAINT_LENGTH[0]; + auto x_1 = UnivariateView(extended_edges.w_r); + auto y_1 = UnivariateView(extended_edges.w_o); + + auto x_2 = UnivariateView(extended_edges.w_l_shift); + auto y_2 = UnivariateView(extended_edges.w_4_shift); + auto x_3 = UnivariateView(extended_edges.w_r_shift); + + auto q_sign = UnivariateView(extended_edges.q_l); + auto q_beta = UnivariateView(extended_edges.q_o); + auto q_beta_sqr = UnivariateView(extended_edges.q_4); + auto q_elliptic = UnivariateView(extended_edges.q_elliptic); + + auto beta_term = x_2 * x_1 * (x_3 + x_3 + x_1) * FF(-1); // -x_1 * x_2 * (2 * x_3 + x_1) + auto beta_sqr_term = x_2 * x_2; // x_2^2 + auto leftovers = beta_sqr_term; // x_2^2 + beta_sqr_term *= (x_3 - x_1); // x_2^2 * (x_3 - x_1) + auto sign_term = y_2 * y_1; // y_1 * y_2 + sign_term += sign_term; // 2 * y_1 * y_2 + beta_term *= q_beta; // -β * x_1 * x_2 * (2 * x_3 + x_1) + beta_sqr_term *= q_beta_sqr; // β^2 * x_2^2 * (x_3 - x_1) + sign_term *= q_sign; // 2 * y_1 * y_2 * sign + leftovers *= x_2; // x_2^3 + leftovers += x_1 * x_1 * (x_3 + x_1); // x_2^3 + x_1 * (x_3 + x_1) + leftovers -= (y_2 * y_2 + y_1 * y_1); // x_2^3 + x_1 * (x_3 + x_1) - y_2^2 - y_1^2 + + // Can be found in class description + auto x_identity = beta_term + beta_sqr_term + sign_term + leftovers; + x_identity *= q_elliptic; + x_identity *= scaling_factor; + std::get<0>(evals) += x_identity; + } + // Contribution (2) + { + static constexpr size_t LENGTH = CONSTRAINT_LENGTH[1]; + auto x_1 = UnivariateView(extended_edges.w_r); + auto y_1 = UnivariateView(extended_edges.w_o); + + auto x_2 = UnivariateView(extended_edges.w_l_shift); + auto y_2 = UnivariateView(extended_edges.w_4_shift); + auto x_3 = UnivariateView(extended_edges.w_r_shift); + auto y_3 = UnivariateView(extended_edges.w_o_shift); + + auto q_sign = UnivariateView(extended_edges.q_l); + auto q_beta = UnivariateView(extended_edges.q_o); + auto q_elliptic = UnivariateView(extended_edges.q_elliptic); + + auto beta_term = x_2 * (y_3 + y_1) * q_beta; // β * x_2 * (y_3 + y_1) + auto sign_term = y_2 * (x_1 - x_3) * q_sign * FF(-1); // - signt * y_2 * (x_1 - x_3) + // TODO: remove extra additions if we decide to stay with this implementation + auto leftovers = x_1 * (y_3 + y_1) * FF(-1) + y_1 * (x_1 - x_3); // -x_1 * y_3 - x_1 * y_1 + y_1 * x_1 - y_1 * x_3 + + auto y_identity = beta_term + sign_term + leftovers; + y_identity *= q_elliptic; + y_identity *= scaling_factor; + std::get<1>(evals) += y_identity; + } }; - void add_full_relation_value_contribution(FF& full_honk_relation_value, + /** + * @brief Add the result of each identity in this relation evaluated at the multivariate evaluations produced by the + * Sumcheck Prover. + * + * @param full_honk_relation_value + * @param purported_evaluations + */ + void add_full_relation_value_contribution(RelationValues& full_honk_relation_value, const auto& purported_evaluations, const RelationParameters&) const { @@ -96,9 +121,7 @@ template class EllipticRelation { auto q_beta_sqr = purported_evaluations.q_4; auto q_elliptic = purported_evaluations.q_elliptic; - static const FF fake_alpha_1 = FF(1); - static const FF fake_alpha_2 = fake_alpha_1 * fake_alpha_1; - + // Contribution (1) auto beta_term = x_2 * x_1 * (x_3 + x_3 + x_1) * FF(-1); // -x_1 * x_2 * (2 * x_3 + x_1) auto beta_sqr_term = x_2 * x_2; // x_2^2 auto leftovers = beta_sqr_term; // x_2^2 @@ -114,20 +137,19 @@ template class EllipticRelation { // Can be found in class description auto x_identity = beta_term + beta_sqr_term + sign_term + leftovers; - x_identity *= fake_alpha_1; + x_identity *= q_elliptic; + std::get<0>(full_honk_relation_value) += x_identity; + // Contribution (2) beta_term = x_2 * (y_3 + y_1) * q_beta; // β * x_2 * (y_3 + y_1) sign_term = y_2 * (x_1 - x_3) * q_sign * FF(-1); // - signt * y_2 * (x_1 - x_3) // TODO: remove extra additions if we decide to stay with this implementation leftovers = x_1 * (y_3 + y_1) * FF(-1) + y_1 * (x_1 - x_3); // -x_1 * y_3 - x_1 * y_1 + y_1 * x_1 - y_1 * x_3 auto y_identity = beta_term + sign_term + leftovers; - y_identity *= fake_alpha_2; - - auto tmp = x_identity + y_identity; - tmp *= q_elliptic; - - full_honk_relation_value += tmp; + y_identity *= q_elliptic; + std::get<1>(full_honk_relation_value) += y_identity; }; }; +// clang-format on } // namespace proof_system::honk::sumcheck diff --git a/cpp/src/barretenberg/honk/sumcheck/relations/gen_perm_sort_relation.hpp b/cpp/src/barretenberg/honk/sumcheck/relations/gen_perm_sort_relation.hpp index 1eb10c0670..d4649acafa 100644 --- a/cpp/src/barretenberg/honk/sumcheck/relations/gen_perm_sort_relation.hpp +++ b/cpp/src/barretenberg/honk/sumcheck/relations/gen_perm_sort_relation.hpp @@ -3,7 +3,7 @@ #include #include "../polynomials/univariate.hpp" -#include "relation.hpp" +#include "relation_parameters.hpp" namespace proof_system::honk::sumcheck { @@ -12,6 +12,15 @@ template class GenPermSortRelation { // 1 + polynomial degree of this relation static constexpr size_t RELATION_LENGTH = 6; // degree(q_sort * D(D - 1)(D - 2)(D - 3)) = 5 + static constexpr size_t NUM_CONSTRAINTS = 4; + static constexpr std::array CONSTRAINT_LENGTH = { 6, 6, 6, 6 }; + + using RelationUnivariates = std::tuple, + Univariate, + Univariate, + Univariate>; + using RelationValues = std::array; + /** * @brief Expression for the generalized permutation sort gate. * @details The relation is defined as C(extended_edges(X)...) = @@ -27,7 +36,7 @@ template class GenPermSortRelation { * @param parameters contains beta, gamma, and public_input_delta, .... * @param scaling_factor optional term to scale the evaluation before adding to evals. */ - void add_edge_contribution(Univariate& evals, + void add_edge_contribution(RelationUnivariates& evals, const auto& extended_edges, const RelationParameters&, const FF& scaling_factor) const @@ -46,53 +55,57 @@ template class GenPermSortRelation { static const FF minus_two = FF(-2); static const FF minus_three = FF(-3); - // TODO(#427): Eventually this would be based on real alpha but this is not a full solution - // since utilizing powers of alpha internal to a relation results in incorrect powers - // being used in the ultimate univariate batching. i.e we'd wind up reusing the same power - // of alpha in multiple relations. - static const FF fake_alpha_1 = FF(1); - static const FF fake_alpha_2 = fake_alpha_1 * fake_alpha_1; - static const FF fake_alpha_3 = fake_alpha_2 * fake_alpha_1; - static const FF fake_alpha_4 = fake_alpha_3 * fake_alpha_1; - // Compute wire differences auto delta_1 = w_2 - w_1; auto delta_2 = w_3 - w_2; auto delta_3 = w_4 - w_3; auto delta_4 = w_1_shift - w_4; + // Contribution (1) auto tmp_1 = delta_1; tmp_1 *= (delta_1 + minus_one); tmp_1 *= (delta_1 + minus_two); tmp_1 *= (delta_1 + minus_three); - tmp_1 *= fake_alpha_1; // 1 + tmp_1 *= q_sort; + tmp_1 *= scaling_factor; + std::get<0>(evals) += tmp_1; + // Contribution (2) auto tmp_2 = delta_2; tmp_2 *= (delta_2 + minus_one); tmp_2 *= (delta_2 + minus_two); tmp_2 *= (delta_2 + minus_three); - tmp_2 *= fake_alpha_2; // alpha + tmp_2 *= q_sort; + tmp_2 *= scaling_factor; + std::get<1>(evals) += tmp_2; + // Contribution (3) auto tmp_3 = delta_3; tmp_3 *= (delta_3 + minus_one); tmp_3 *= (delta_3 + minus_two); tmp_3 *= (delta_3 + minus_three); - tmp_3 *= fake_alpha_3; // alpha^2 + tmp_3 *= q_sort; + tmp_3 *= scaling_factor; + std::get<2>(evals) += tmp_3; + // Contribution (4) auto tmp_4 = delta_4; tmp_4 *= (delta_4 + minus_one); tmp_4 *= (delta_4 + minus_two); tmp_4 *= (delta_4 + minus_three); - tmp_4 *= fake_alpha_4; // alpha^3 - - auto tmp = tmp_1 + tmp_2 + tmp_3 + tmp_4; - tmp *= q_sort; - tmp *= scaling_factor; - - evals += tmp; + tmp_4 *= q_sort; + tmp_4 *= scaling_factor; + std::get<3>(evals) += tmp_4; }; - void add_full_relation_value_contribution(FF& full_honk_relation_value, + /** + * @brief Add the result of each identity in this relation evaluated at the multivariate evaluations produced by the + * Sumcheck Prover. + * + * @param full_honk_relation_value + * @param purported_evaluations + */ + void add_full_relation_value_contribution(RelationValues& full_honk_relation_value, const auto& purported_evaluations, const RelationParameters&) const { @@ -107,15 +120,6 @@ template class GenPermSortRelation { static const FF minus_two = FF(-2); static const FF minus_three = FF(-3); - // TODO(#427): Eventually this would be based on real alpha but this is not a full solution - // since utilizing powers of alpha internal to a relation results in incorrect powers - // being used in the ultimate univariate batching. i.e we'd wind up reusing the same power - // of alpha in multiple relations. - static const FF fake_alpha_1 = FF(1); - static const FF fake_alpha_2 = fake_alpha_1 * fake_alpha_1; - static const FF fake_alpha_3 = fake_alpha_2 * fake_alpha_1; - static const FF fake_alpha_4 = fake_alpha_3 * fake_alpha_1; - // Compute wire differences auto delta_1 = w_2 - w_1; auto delta_2 = w_3 - w_2; @@ -126,30 +130,29 @@ template class GenPermSortRelation { tmp_1 *= (delta_1 + minus_one); tmp_1 *= (delta_1 + minus_two); tmp_1 *= (delta_1 + minus_three); - tmp_1 *= fake_alpha_1; // 1 + tmp_1 *= q_sort; + std::get<0>(full_honk_relation_value) += tmp_1; auto tmp_2 = delta_2; tmp_2 *= (delta_2 + minus_one); tmp_2 *= (delta_2 + minus_two); tmp_2 *= (delta_2 + minus_three); - tmp_2 *= fake_alpha_2; // alpha + tmp_2 *= q_sort; + std::get<1>(full_honk_relation_value) += tmp_2; auto tmp_3 = delta_3; tmp_3 *= (delta_3 + minus_one); tmp_3 *= (delta_3 + minus_two); tmp_3 *= (delta_3 + minus_three); - tmp_3 *= fake_alpha_3; // alpha^2 + tmp_3 *= q_sort; + std::get<2>(full_honk_relation_value) += tmp_3; auto tmp_4 = delta_4; tmp_4 *= (delta_4 + minus_one); tmp_4 *= (delta_4 + minus_two); tmp_4 *= (delta_4 + minus_three); - tmp_4 *= fake_alpha_4; // alpha^3 - - auto tmp = tmp_1 + tmp_2 + tmp_3 + tmp_4; - tmp *= q_sort; - - full_honk_relation_value += tmp; + tmp_4 *= q_sort; + std::get<3>(full_honk_relation_value) += tmp_4; }; }; } // namespace proof_system::honk::sumcheck diff --git a/cpp/src/barretenberg/honk/sumcheck/relations/grand_product_initialization_relation.hpp b/cpp/src/barretenberg/honk/sumcheck/relations/grand_product_initialization_relation.hpp deleted file mode 100644 index 0fd8fbd606..0000000000 --- a/cpp/src/barretenberg/honk/sumcheck/relations/grand_product_initialization_relation.hpp +++ /dev/null @@ -1,89 +0,0 @@ -#pragma once -#include "relation.hpp" -#include "../polynomials/univariate.hpp" - -namespace proof_system::honk::sumcheck { - -template class GrandProductInitializationRelation { - public: - // 1 + polynomial degree of this relation - static constexpr size_t RELATION_LENGTH = 3; - - /** - * @brief Add contribution of the permutation relation for a given edge - * - * @details There are 2 relations associated with enforcing the wire copy relations - * This file handles the relation Z_perm_shift(n_last) = 0 via the relation: - * - * C(X) = L_LAST(X) * Z_perm_shift(X) - * - * @param evals transformed to `evals + C(extended_edges(X)...)*scaling_factor` - * @param extended_edges an std::array containing the fully extended Univariate edges. - * @param parameters contains beta, gamma, and public_input_delta, .... - * @param scaling_factor optional term to scale the evaluation before adding to evals. - */ - void add_edge_contribution(Univariate& evals, - const auto& extended_edges, - const RelationParameters&, - const FF& scaling_factor) const - { - auto z_perm_shift = UnivariateView(extended_edges.z_perm_shift); - auto lagrange_last = UnivariateView(extended_edges.lagrange_last); - - evals += (lagrange_last * z_perm_shift) * scaling_factor; - }; - - void add_full_relation_value_contribution(FF& full_honk_relation_value, - auto& purported_evaluations, - const RelationParameters&) const - { - auto z_perm_shift = purported_evaluations.z_perm_shift; - auto lagrange_last = purported_evaluations.lagrange_last; - - full_honk_relation_value += lagrange_last * z_perm_shift; - }; -}; - -// TODO(luke): The only difference between the Ultra relation and the Standard version is the enum -// used to refer into the edge polynomials. Seems desireable to not duplicate the code here but -// leaving this as is until Codys Flavor work is settled. -template class UltraGrandProductInitializationRelation { - public: - // 1 + polynomial degree of this relation - static constexpr size_t RELATION_LENGTH = 3; - - /** - * @brief Add contribution of the permutation relation for a given edge - * - * @details There are 2 relations associated with enforcing the wire copy relations - * This file handles the relation Z_perm_shift(n_last) = 0 via the relation: - * - * C(X) = L_LAST(X) * Z_perm_shift(X) - * - * @param evals transformed to `evals + C(extended_edges(X)...)*scaling_factor` - * @param extended_edges an std::array containing the fully extended Univariate edges. - * @param parameters contains beta, gamma, and public_input_delta, .... - * @param scaling_factor optional term to scale the evaluation before adding to evals. - */ - void add_edge_contribution(Univariate& evals, - const auto& extended_edges, - const RelationParameters&, - const FF& scaling_factor) const - { - auto z_perm_shift = UnivariateView(extended_edges.z_perm_shift); - auto lagrange_last = UnivariateView(extended_edges.lagrange_last); - - evals += (lagrange_last * z_perm_shift) * scaling_factor; - }; - - void add_full_relation_value_contribution(FF& full_honk_relation_value, - auto& purported_evaluations, - const RelationParameters&) const - { - auto z_perm_shift = purported_evaluations.z_perm_shift; - auto lagrange_last = purported_evaluations.lagrange_last; - - full_honk_relation_value += lagrange_last * z_perm_shift; - }; -}; -} // namespace proof_system::honk::sumcheck diff --git a/cpp/src/barretenberg/honk/sumcheck/relations/lookup_grand_product_relation.hpp b/cpp/src/barretenberg/honk/sumcheck/relations/lookup_relation.hpp similarity index 50% rename from cpp/src/barretenberg/honk/sumcheck/relations/lookup_grand_product_relation.hpp rename to cpp/src/barretenberg/honk/sumcheck/relations/lookup_relation.hpp index 32830a8dc3..b08a2bdedd 100644 --- a/cpp/src/barretenberg/honk/sumcheck/relations/lookup_grand_product_relation.hpp +++ b/cpp/src/barretenberg/honk/sumcheck/relations/lookup_relation.hpp @@ -1,5 +1,5 @@ #pragma once -#include "relation.hpp" +#include "relation_parameters.hpp" #include "../polynomials/univariate.hpp" #pragma GCC diagnostic ignored "-Wunused-variable" @@ -7,11 +7,17 @@ namespace proof_system::honk::sumcheck { -template class LookupGrandProductComputationRelation { +template class LookupRelation { public: // 1 + polynomial degree of this relation static constexpr size_t RELATION_LENGTH = 6; // deg(z_lookup * column_selector * wire * q_lookup * table) = 5 + static constexpr size_t NUM_CONSTRAINTS = 2; + static constexpr std::array CONSTRAINT_LENGTH = { 6, 3 }; + + using RelationUnivariates = std::tuple, Univariate>; + using RelationValues = std::array; + /** * @brief Compute contribution of the lookup grand prod relation for a given edge (internal function) * @@ -30,7 +36,7 @@ template class LookupGrandProductComputationRelation { * @param parameters contains beta, gamma, and public_input_delta, .... * @param scaling_factor optional term to scale the evaluation before adding to evals. */ - inline void add_edge_contribution(Univariate& evals, + inline void add_edge_contribution(RelationUnivariates& evals, const auto& extended_edges, const RelationParameters& relation_parameters, const FF& scaling_factor) const @@ -45,60 +51,78 @@ template class LookupGrandProductComputationRelation { const auto eta_sqr = eta * eta; const auto eta_cube = eta_sqr * eta; - auto w_1 = UnivariateView(extended_edges.w_l); - auto w_2 = UnivariateView(extended_edges.w_r); - auto w_3 = UnivariateView(extended_edges.w_o); - - auto w_1_shift = UnivariateView(extended_edges.w_l_shift); - auto w_2_shift = UnivariateView(extended_edges.w_r_shift); - auto w_3_shift = UnivariateView(extended_edges.w_o_shift); - - auto table_1 = UnivariateView(extended_edges.table_1); - auto table_2 = UnivariateView(extended_edges.table_2); - auto table_3 = UnivariateView(extended_edges.table_3); - auto table_4 = UnivariateView(extended_edges.table_4); - - auto table_1_shift = UnivariateView(extended_edges.table_1_shift); - auto table_2_shift = UnivariateView(extended_edges.table_2_shift); - auto table_3_shift = UnivariateView(extended_edges.table_3_shift); - auto table_4_shift = UnivariateView(extended_edges.table_4_shift); - - auto s_accum = UnivariateView(extended_edges.sorted_accum); - auto s_accum_shift = UnivariateView(extended_edges.sorted_accum_shift); - - auto z_lookup = UnivariateView(extended_edges.z_lookup); - auto z_lookup_shift = UnivariateView(extended_edges.z_lookup_shift); - - auto table_index = UnivariateView(extended_edges.q_o); - auto column_1_step_size = UnivariateView(extended_edges.q_r); - auto column_2_step_size = UnivariateView(extended_edges.q_m); - auto column_3_step_size = UnivariateView(extended_edges.q_c); - auto q_lookup = UnivariateView(extended_edges.q_lookup); - - auto lagrange_first = UnivariateView(extended_edges.lagrange_first); - auto lagrange_last = UnivariateView(extended_edges.lagrange_last); - - // (w_1 + q_2*w_1_shift) + η(w_2 + q_m*w_2_shift) + η²(w_3 + q_c*w_3_shift) + η³q_index. - auto wire_accum = (w_1 + column_1_step_size * w_1_shift) + (w_2 + column_2_step_size * w_2_shift) * eta + - (w_3 + column_3_step_size * w_3_shift) * eta_sqr + table_index * eta_cube; - - // t_1 + ηt_2 + η²t_3 + η³t_4 - auto table_accum = table_1 + table_2 * eta + table_3 * eta_sqr + table_4 * eta_cube; - // t_1_shift + ηt_2_shift + η²t_3_shift + η³t_4_shift - auto table_accum_shift = - table_1_shift + table_2_shift * eta + table_3_shift * eta_sqr + table_4_shift * eta_cube; - // Contribution (1) - auto tmp = (q_lookup * wire_accum + gamma); - tmp *= (table_accum + table_accum_shift * beta + gamma_by_one_plus_beta); - tmp *= one_plus_beta; - tmp *= (z_lookup + lagrange_first); - tmp -= (z_lookup_shift + lagrange_last * grand_product_delta) * - (s_accum + s_accum_shift * beta + gamma_by_one_plus_beta); - evals += tmp * scaling_factor; + { + static constexpr size_t LENGTH = CONSTRAINT_LENGTH[0]; + auto w_1 = UnivariateView(extended_edges.w_l); + auto w_2 = UnivariateView(extended_edges.w_r); + auto w_3 = UnivariateView(extended_edges.w_o); + + auto w_1_shift = UnivariateView(extended_edges.w_l_shift); + auto w_2_shift = UnivariateView(extended_edges.w_r_shift); + auto w_3_shift = UnivariateView(extended_edges.w_o_shift); + + auto table_1 = UnivariateView(extended_edges.table_1); + auto table_2 = UnivariateView(extended_edges.table_2); + auto table_3 = UnivariateView(extended_edges.table_3); + auto table_4 = UnivariateView(extended_edges.table_4); + + auto table_1_shift = UnivariateView(extended_edges.table_1_shift); + auto table_2_shift = UnivariateView(extended_edges.table_2_shift); + auto table_3_shift = UnivariateView(extended_edges.table_3_shift); + auto table_4_shift = UnivariateView(extended_edges.table_4_shift); + + auto s_accum = UnivariateView(extended_edges.sorted_accum); + auto s_accum_shift = UnivariateView(extended_edges.sorted_accum_shift); + + auto z_lookup = UnivariateView(extended_edges.z_lookup); + auto z_lookup_shift = UnivariateView(extended_edges.z_lookup_shift); + + auto table_index = UnivariateView(extended_edges.q_o); + auto column_1_step_size = UnivariateView(extended_edges.q_r); + auto column_2_step_size = UnivariateView(extended_edges.q_m); + auto column_3_step_size = UnivariateView(extended_edges.q_c); + auto q_lookup = UnivariateView(extended_edges.q_lookup); + + auto lagrange_first = UnivariateView(extended_edges.lagrange_first); + auto lagrange_last = UnivariateView(extended_edges.lagrange_last); + + // (w_1 + q_2*w_1_shift) + η(w_2 + q_m*w_2_shift) + η²(w_3 + q_c*w_3_shift) + η³q_index. + auto wire_accum = (w_1 + column_1_step_size * w_1_shift) + (w_2 + column_2_step_size * w_2_shift) * eta + + (w_3 + column_3_step_size * w_3_shift) * eta_sqr + table_index * eta_cube; + + // t_1 + ηt_2 + η²t_3 + η³t_4 + auto table_accum = table_1 + table_2 * eta + table_3 * eta_sqr + table_4 * eta_cube; + // t_1_shift + ηt_2_shift + η²t_3_shift + η³t_4_shift + auto table_accum_shift = + table_1_shift + table_2_shift * eta + table_3_shift * eta_sqr + table_4_shift * eta_cube; + + auto tmp = (q_lookup * wire_accum + gamma); + tmp *= (table_accum + table_accum_shift * beta + gamma_by_one_plus_beta); + tmp *= one_plus_beta; + tmp *= (z_lookup + lagrange_first); + tmp -= (z_lookup_shift + lagrange_last * grand_product_delta) * + (s_accum + s_accum_shift * beta + gamma_by_one_plus_beta); + std::get<0>(evals) += tmp * scaling_factor; + } + { + static constexpr size_t LENGTH = CONSTRAINT_LENGTH[1]; + auto z_lookup_shift = UnivariateView(extended_edges.z_lookup_shift); + auto lagrange_last = UnivariateView(extended_edges.lagrange_last); + + // Contribution (2) + std::get<1>(evals) += (lagrange_last * z_lookup_shift) * scaling_factor; + } }; - void add_full_relation_value_contribution(FF& full_honk_relation_value, + /** + * @brief Add the result of each identity in this relation evaluated at the multivariate evaluations produced by the + * Sumcheck Prover. + * + * @param full_honk_relation_value + * @param purported_evaluations + */ + void add_full_relation_value_contribution(RelationValues& full_honk_relation_value, auto& purported_evaluations, const RelationParameters& relation_parameters) const { @@ -162,45 +186,10 @@ template class LookupGrandProductComputationRelation { tmp *= (z_lookup + lagrange_first); tmp -= (z_lookup_shift + lagrange_last * grand_product_delta) * (s_accum + beta * s_accum_shift + gamma_by_one_plus_beta); - full_honk_relation_value += tmp; - }; -}; - -template class LookupGrandProductInitializationRelation { - public: - // 1 + polynomial degree of this relation - static constexpr size_t RELATION_LENGTH = 3; // deg(lagrange_last * z_lookup_shift) = 2 - - /** - * @brief Compute contribution of the lookup grand prod relation for a given edge (internal function) - * - * @details This the relation confirms correct initialization of the lookup grand - * product polynomial Z_lookup with Z_lookup[circuit_size] = 0. - * - * @param evals transformed to `evals + C(extended_edges(X)...)*scaling_factor` - * @param extended_edges an std::array containing the fully extended Univariate edges. - * @param parameters contains beta, gamma, and public_input_delta, .... - * @param scaling_factor optional term to scale the evaluation before adding to evals. - */ - inline void add_edge_contribution(Univariate& evals, - const auto& extended_edges, - const RelationParameters& /*unused*/, - const FF& scaling_factor) const - { - auto z_lookup_shift = UnivariateView(extended_edges.z_lookup_shift); - auto lagrange_last = UnivariateView(extended_edges.lagrange_last); - - evals += (lagrange_last * z_lookup_shift) * scaling_factor; - }; - - void add_full_relation_value_contribution(FF& full_honk_relation_value, - auto& purported_evaluations, - const RelationParameters& /*unused*/) const - { - auto z_lookup_shift = purported_evaluations.z_lookup_shift; - auto lagrange_last = purported_evaluations.lagrange_last; + std::get<0>(full_honk_relation_value) += tmp; - full_honk_relation_value += lagrange_last * z_lookup_shift; + // Contribution (2) + std::get<1>(full_honk_relation_value) += lagrange_last * z_lookup_shift; }; }; } // namespace proof_system::honk::sumcheck \ No newline at end of file diff --git a/cpp/src/barretenberg/honk/sumcheck/relations/grand_product_computation_relation.hpp b/cpp/src/barretenberg/honk/sumcheck/relations/permutation_relation.hpp similarity index 50% rename from cpp/src/barretenberg/honk/sumcheck/relations/grand_product_computation_relation.hpp rename to cpp/src/barretenberg/honk/sumcheck/relations/permutation_relation.hpp index ff2bc0ea8f..f613f0c3b6 100644 --- a/cpp/src/barretenberg/honk/sumcheck/relations/grand_product_computation_relation.hpp +++ b/cpp/src/barretenberg/honk/sumcheck/relations/permutation_relation.hpp @@ -1,15 +1,21 @@ #pragma once -#include "relation.hpp" +#include "relation_parameters.hpp" #include "../polynomials/univariate.hpp" // TODO(luke): change name of this file to permutation_grand_product_relation(s).hpp and move 'init' relation into it. namespace proof_system::honk::sumcheck { -template class GrandProductComputationRelation { +template class PermutationRelation { public: // 1 + polynomial degree of this relation static constexpr size_t RELATION_LENGTH = 5; + static constexpr size_t NUM_CONSTRAINTS = 2; + static constexpr std::array CONSTRAINT_LENGTH = { 5, 3 }; + + using RelationUnivariates = std::tuple, Univariate>; + using RelationValues = std::array; + /** * @brief Compute contribution of the permutation relation for a given edge (internal function) * @@ -28,7 +34,7 @@ template class GrandProductComputationRelation { * @param parameters contains beta, gamma, and public_input_delta, .... * @param scaling_factor optional term to scale the evaluation before adding to evals. */ - inline void add_edge_contribution(Univariate& evals, + inline void add_edge_contribution(RelationUnivariates& evals, const auto& extended_edges, const RelationParameters& relation_parameters, const FF& scaling_factor) const @@ -37,29 +43,48 @@ template class GrandProductComputationRelation { const auto& gamma = relation_parameters.gamma; const auto& public_input_delta = relation_parameters.public_input_delta; - auto w_1 = UnivariateView(extended_edges.w_l); - auto w_2 = UnivariateView(extended_edges.w_r); - auto w_3 = UnivariateView(extended_edges.w_o); - auto sigma_1 = UnivariateView(extended_edges.sigma_1); - auto sigma_2 = UnivariateView(extended_edges.sigma_2); - auto sigma_3 = UnivariateView(extended_edges.sigma_3); - auto id_1 = UnivariateView(extended_edges.id_1); - auto id_2 = UnivariateView(extended_edges.id_2); - auto id_3 = UnivariateView(extended_edges.id_3); - auto z_perm = UnivariateView(extended_edges.z_perm); - auto z_perm_shift = UnivariateView(extended_edges.z_perm_shift); - auto lagrange_first = UnivariateView(extended_edges.lagrange_first); - auto lagrange_last = UnivariateView(extended_edges.lagrange_last); + { + static constexpr size_t LENGTH = CONSTRAINT_LENGTH[0]; + auto w_1 = UnivariateView(extended_edges.w_l); + auto w_2 = UnivariateView(extended_edges.w_r); + auto w_3 = UnivariateView(extended_edges.w_o); + auto sigma_1 = UnivariateView(extended_edges.sigma_1); + auto sigma_2 = UnivariateView(extended_edges.sigma_2); + auto sigma_3 = UnivariateView(extended_edges.sigma_3); + auto id_1 = UnivariateView(extended_edges.id_1); + auto id_2 = UnivariateView(extended_edges.id_2); + auto id_3 = UnivariateView(extended_edges.id_3); + auto z_perm = UnivariateView(extended_edges.z_perm); + auto z_perm_shift = UnivariateView(extended_edges.z_perm_shift); + auto lagrange_first = UnivariateView(extended_edges.lagrange_first); + auto lagrange_last = UnivariateView(extended_edges.lagrange_last); - // Contribution (1) - evals += (((z_perm + lagrange_first) * (w_1 + id_1 * beta + gamma) * (w_2 + id_2 * beta + gamma) * - (w_3 + id_3 * beta + gamma)) - - ((z_perm_shift + lagrange_last * public_input_delta) * (w_1 + sigma_1 * beta + gamma) * - (w_2 + sigma_2 * beta + gamma) * (w_3 + sigma_3 * beta + gamma))) * - scaling_factor; + // Contribution (1) + std::get<0>(evals) += + (((z_perm + lagrange_first) * (w_1 + id_1 * beta + gamma) * (w_2 + id_2 * beta + gamma) * + (w_3 + id_3 * beta + gamma)) - + ((z_perm_shift + lagrange_last * public_input_delta) * (w_1 + sigma_1 * beta + gamma) * + (w_2 + sigma_2 * beta + gamma) * (w_3 + sigma_3 * beta + gamma))) * + scaling_factor; + } + { + static constexpr size_t LENGTH = CONSTRAINT_LENGTH[1]; + auto z_perm_shift = UnivariateView(extended_edges.z_perm_shift); + auto lagrange_last = UnivariateView(extended_edges.lagrange_last); + + // Contribution (2) + std::get<1>(evals) += (lagrange_last * z_perm_shift) * scaling_factor; + } }; - void add_full_relation_value_contribution(FF& full_honk_relation_value, + /** + * @brief Add the result of each identity in this relation evaluated at the multivariate evaluations produced by the + * Sumcheck Prover. + * + * @param full_honk_relation_value + * @param purported_evaluations + */ + void add_full_relation_value_contribution(RelationValues& full_honk_relation_value, auto& purported_evaluations, const RelationParameters& relation_parameters) const { @@ -82,21 +107,30 @@ template class GrandProductComputationRelation { auto lagrange_last = purported_evaluations.lagrange_last; // Contribution (1) - full_honk_relation_value += + std::get<0>(full_honk_relation_value) += ((z_perm + lagrange_first) * (w_1 + beta * id_1 + gamma) * (w_2 + beta * id_2 + gamma) * (w_3 + beta * id_3 + gamma) - (z_perm_shift + lagrange_last * public_input_delta) * (w_1 + beta * sigma_1 + gamma) * (w_2 + beta * sigma_2 + gamma) * (w_3 + beta * sigma_3 + gamma)); + + // Contribution (2) + std::get<1>(full_honk_relation_value) += lagrange_last * z_perm_shift; }; }; // TODO(luke): With Cody's Flavor work it should be easier to create a simple templated relation // for handling arbitrary width. For now I'm duplicating the width 3 logic for width 4. -template class UltraGrandProductComputationRelation { +template class UltraPermutationRelation { public: // 1 + polynomial degree of this relation static constexpr size_t RELATION_LENGTH = 6; + static constexpr size_t NUM_CONSTRAINTS = 2; + static constexpr std::array CONSTRAINT_LENGTH = { 6, 3 }; + + using RelationUnivariates = std::tuple, Univariate>; + using RelationValues = std::array; + /** * @brief Compute contribution of the permutation relation for a given edge (internal function) * @@ -108,7 +142,7 @@ template class UltraGrandProductComputationRelation { * @param parameters contains beta, gamma, and public_input_delta, .... * @param scaling_factor optional term to scale the evaluation before adding to evals. */ - inline void add_edge_contribution(Univariate& evals, + inline void add_edge_contribution(RelationUnivariates& evals, const auto& extended_edges, const RelationParameters& relation_parameters, const FF& scaling_factor) const @@ -117,32 +151,51 @@ template class UltraGrandProductComputationRelation { const auto& gamma = relation_parameters.gamma; const auto& public_input_delta = relation_parameters.public_input_delta; - auto w_1 = UnivariateView(extended_edges.w_l); - auto w_2 = UnivariateView(extended_edges.w_r); - auto w_3 = UnivariateView(extended_edges.w_o); - auto w_4 = UnivariateView(extended_edges.w_4); - auto sigma_1 = UnivariateView(extended_edges.sigma_1); - auto sigma_2 = UnivariateView(extended_edges.sigma_2); - auto sigma_3 = UnivariateView(extended_edges.sigma_3); - auto sigma_4 = UnivariateView(extended_edges.sigma_4); - auto id_1 = UnivariateView(extended_edges.id_1); - auto id_2 = UnivariateView(extended_edges.id_2); - auto id_3 = UnivariateView(extended_edges.id_3); - auto id_4 = UnivariateView(extended_edges.id_4); - auto z_perm = UnivariateView(extended_edges.z_perm); - auto z_perm_shift = UnivariateView(extended_edges.z_perm_shift); - auto lagrange_first = UnivariateView(extended_edges.lagrange_first); - auto lagrange_last = UnivariateView(extended_edges.lagrange_last); - // Contribution (1) - evals += (((z_perm + lagrange_first) * (w_1 + id_1 * beta + gamma) * (w_2 + id_2 * beta + gamma) * - (w_3 + id_3 * beta + gamma) * (w_4 + id_4 * beta + gamma)) - - ((z_perm_shift + lagrange_last * public_input_delta) * (w_1 + sigma_1 * beta + gamma) * - (w_2 + sigma_2 * beta + gamma) * (w_3 + sigma_3 * beta + gamma) * (w_4 + sigma_4 * beta + gamma))) * - scaling_factor; + { + static constexpr size_t LENGTH = CONSTRAINT_LENGTH[0]; + auto w_1 = UnivariateView(extended_edges.w_l); + auto w_2 = UnivariateView(extended_edges.w_r); + auto w_3 = UnivariateView(extended_edges.w_o); + auto w_4 = UnivariateView(extended_edges.w_4); + auto sigma_1 = UnivariateView(extended_edges.sigma_1); + auto sigma_2 = UnivariateView(extended_edges.sigma_2); + auto sigma_3 = UnivariateView(extended_edges.sigma_3); + auto sigma_4 = UnivariateView(extended_edges.sigma_4); + auto id_1 = UnivariateView(extended_edges.id_1); + auto id_2 = UnivariateView(extended_edges.id_2); + auto id_3 = UnivariateView(extended_edges.id_3); + auto id_4 = UnivariateView(extended_edges.id_4); + auto z_perm = UnivariateView(extended_edges.z_perm); + auto z_perm_shift = UnivariateView(extended_edges.z_perm_shift); + auto lagrange_first = UnivariateView(extended_edges.lagrange_first); + auto lagrange_last = UnivariateView(extended_edges.lagrange_last); + + std::get<0>(evals) += + (((z_perm + lagrange_first) * (w_1 + id_1 * beta + gamma) * (w_2 + id_2 * beta + gamma) * + (w_3 + id_3 * beta + gamma) * (w_4 + id_4 * beta + gamma)) - + ((z_perm_shift + lagrange_last * public_input_delta) * (w_1 + sigma_1 * beta + gamma) * + (w_2 + sigma_2 * beta + gamma) * (w_3 + sigma_3 * beta + gamma) * (w_4 + sigma_4 * beta + gamma))) * + scaling_factor; + } + // Contribution (2) + { + static constexpr size_t LENGTH = CONSTRAINT_LENGTH[1]; + auto z_perm_shift = UnivariateView(extended_edges.z_perm_shift); + auto lagrange_last = UnivariateView(extended_edges.lagrange_last); + + std::get<1>(evals) += (lagrange_last * z_perm_shift) * scaling_factor; + } }; - void add_full_relation_value_contribution(FF& full_honk_relation_value, + /** + * @brief Add the result of each identity in this relation evaluated at the multivariate evaluations produced by the + * Sumcheck Prover. + * + * @param full_honk_relation_value + * @param purported_evaluations + */ + void add_full_relation_value_contribution(RelationValues& full_honk_relation_value, auto& purported_evaluations, const RelationParameters& relation_parameters) const { @@ -168,11 +221,14 @@ template class UltraGrandProductComputationRelation { auto lagrange_last = purported_evaluations.lagrange_last; // Contribution (1) - full_honk_relation_value += + std::get<0>(full_honk_relation_value) += ((z_perm + lagrange_first) * (w_1 + beta * id_1 + gamma) * (w_2 + beta * id_2 + gamma) * (w_3 + beta * id_3 + gamma) * (w_4 + beta * id_4 + gamma) - (z_perm_shift + lagrange_last * public_input_delta) * (w_1 + beta * sigma_1 + gamma) * (w_2 + beta * sigma_2 + gamma) * (w_3 + beta * sigma_3 + gamma) * (w_4 + beta * sigma_4 + gamma)); + + // Contribution (2) + std::get<1>(full_honk_relation_value) += lagrange_last * z_perm_shift; }; }; } // namespace proof_system::honk::sumcheck diff --git a/cpp/src/barretenberg/honk/sumcheck/relations/relation_consistency.test.cpp b/cpp/src/barretenberg/honk/sumcheck/relations/relation_consistency.test.cpp index 0747a5b4f0..f794b16603 100644 --- a/cpp/src/barretenberg/honk/sumcheck/relations/relation_consistency.test.cpp +++ b/cpp/src/barretenberg/honk/sumcheck/relations/relation_consistency.test.cpp @@ -1,11 +1,9 @@ -#include "barretenberg/honk/sumcheck/relations/lookup_grand_product_relation.hpp" +#include "barretenberg/honk/sumcheck/relations/lookup_relation.hpp" #include "barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation.hpp" -#include "barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation_secondary.hpp" #include "barretenberg/honk/flavor/standard.hpp" -#include "relation.hpp" +#include "relation_parameters.hpp" #include "arithmetic_relation.hpp" -#include "grand_product_initialization_relation.hpp" -#include "grand_product_computation_relation.hpp" +#include "permutation_relation.hpp" #include "../polynomials/univariate.hpp" #include "../polynomials/barycentric_data.hpp" @@ -16,14 +14,14 @@ #include using namespace proof_system::honk::sumcheck; /** - * We want to test if all three relations (namely, ArithmeticRelation, GrandProductComputationRelation, - * GrandProductInitializationRelation) provide correct contributions by manually computing their - * contributions with deterministic and random inputs. The relations are supposed to work with - * univariates (edges) of degree one (length 2) and spit out polynomials of corresponding degrees. We have - * MAX_RELATION_LENGTH = 5, meaning the output of a relation can atmost be a degree 5 polynomial. Hence, - * we use a method compute_mock_extended_edges() which starts with degree one input polynomial (two evaluation - points), - * extends them (using barycentric formula) to six evaluation points, and stores them to an array of polynomials. + * The purpose of this test suite is to show that the identity arithmetic implemented in the Relations is equivalent to + * a simpler unoptimized version implemented in the tests themselves. This is useful 1) as documentation since the + * simple implementations here should make the underlying arithmetic easier to see, and 2) as a check that optimizations + * introduced into the Relations have not changed the result. + * + * For this purpose, we simply feed (the same) random inputs into each of the two implementations and confirm that + * the outputs match. This does not confirm the correctness of the identity arithmetic (the identities will not be + * satisfied in general by random inputs) only that the two implementations are equivalent. */ static const size_t INPUT_UNIVARIATE_LENGTH = 2; @@ -38,9 +36,9 @@ class StandardRelationConsistency : public testing::Test { template using ExtendedEdges = typename Flavor::template ExtendedEdges; - // TODO(#225)(Adrian): Accept FULL_RELATION_LENGTH as a template parameter for this function only, so that the test - // can decide to which degree the polynomials must be extended. Possible accept an existing list of "edges" and - // extend them to the degree. + // TODO(#225)(Adrian): Accept FULL_RELATION_LENGTH as a template parameter for this function only, so that the + // test can decide to which degree the polynomials must be extended. Possible accept an existing list of + // "edges" and extend them to the degree. template static void compute_mock_extended_edges( ExtendedEdges& extended_edges, @@ -107,34 +105,76 @@ class StandardRelationConsistency : public testing::Test { * @param relation_parameters */ template - static void validate_evaluations(const Univariate& expected_evals, + static void validate_evaluations(const auto& expected_full_length_univariates, /* array of Univariates*/ const auto relation, const ExtendedEdges& extended_edges, const RelationParameters& relation_parameters) { + // First check that the verifier's computation on individual evaluations is correct. + // Note: since add_full_relation_value_contribution computes the identities at a single evaluation of the + // multivariates, we need only pass in one evaluation point from the extended edges. Which one we choose is + // arbitrary so we choose the 0th. - // Compute the expression index-by-index - Univariate expected_evals_index{ 0 }; - for (size_t i = 0; i < FULL_RELATION_LENGTH; ++i) { - // Get an array of the same size as `extended_edges` with only the i-th element of each extended edge. - ClaimedEvaluations evals_i = transposed_univariate_array_at(extended_edges, i); - // Evaluate the relation - relation.add_full_relation_value_contribution( - expected_evals_index.value_at(i), evals_i, relation_parameters); + // Extract the RelationValues type for the given relation + using RelationValues = typename decltype(relation)::RelationValues; + RelationValues relation_evals; + RelationValues expected_relation_evals; + + ASSERT_EQ(expected_relation_evals.size(), expected_full_length_univariates.size()); + // Initialize expected_evals to 0th coefficient of expected full length univariates + for (size_t idx = 0; idx < relation_evals.size(); ++idx) { + relation_evals[idx] = FF(0); // initialize to 0 + expected_relation_evals[idx] = expected_full_length_univariates[idx].value_at(0); } - EXPECT_EQ(expected_evals, expected_evals_index); - // Compute the expression using the class, that converts the extended edges to UnivariateView - auto expected_evals_view = Univariate(0); - // The scaling factor is essentially 1 since we are working with degree 1 univariates - relation.add_edge_contribution(expected_evals_view, extended_edges, relation_parameters, 1); + // Extract 0th evaluation from extended edges + ClaimedEvaluations edge_evaluations = transposed_univariate_array_at(extended_edges, 0); - // Tiny hack to reduce `expected_evals` to be of size `relation.RELATION_LENGTH` - Univariate expected_evals_restricted{ - UnivariateView(expected_evals) - }; - EXPECT_EQ(expected_evals_restricted, expected_evals_view); + // Evaluate the relation using the verifier functionality + relation.add_full_relation_value_contribution(relation_evals, edge_evaluations, relation_parameters); + + EXPECT_EQ(relation_evals, expected_relation_evals); + + // Next, check that the prover's computation on Univariates is correct + + using RelationUnivariates = typename decltype(relation)::RelationUnivariates; + RelationUnivariates relation_univariates; + zero_univariates<>(relation_univariates); + + constexpr std::size_t num_univariates = std::tuple_size::value; + + // Compute the relatiion univariates via the sumcheck prover functionality, then extend + // them to full length for easy comparison with the expected result. + relation.add_edge_contribution(relation_univariates, extended_edges, relation_parameters, 1); + + auto full_length_univariates = std::array, num_univariates>(); + extend_tuple_of_arrays(relation_univariates, full_length_univariates); + + EXPECT_EQ(full_length_univariates, expected_full_length_univariates); }; + + template static void zero_univariates(std::tuple& tuple) + { + auto& element = std::get(tuple); + std::fill(element.evaluations.begin(), element.evaluations.end(), FF(0)); + + if constexpr (idx + 1 < sizeof...(Ts)) { + zero_univariates(tuple); + } + } + + template + static void extend_tuple_of_arrays(std::tuple& tuple, auto& result_univariates) + { + auto& element = std::get(tuple); + using Element = std::remove_reference_t; + BarycentricData barycentric_utils; + result_univariates[idx] = barycentric_utils.extend(element); + + if constexpr (idx + 1 < sizeof...(Ts)) { + extend_tuple_of_arrays(tuple, result_univariates); + } + } }; TEST_F(StandardRelationConsistency, ArithmeticRelation) @@ -174,17 +214,19 @@ TEST_F(StandardRelationConsistency, ArithmeticRelation) const auto& q_o = extended_edges.q_o; const auto& q_c = extended_edges.q_c; - // We first compute the evaluations using UnivariateViews, with the provided hard-coded formula. - // Ensure that expression changes are detected. - // expected_evals, length 4, extends to { { 5, 22, 57, 116, 205} } for input polynomial {1, 2} - auto expected_evals = (q_m * w_r * w_l) + (q_r * w_r) + (q_l * w_l) + (q_o * w_o) + (q_c); - validate_evaluations(expected_evals, relation, extended_edges, relation_parameters); + // Compute expected full length Univariates using straight forward expressions. + // Note: expect { { 5, 22, 57, 116, 205} } for input polynomial {1, 2} + constexpr std::size_t NUM_CONSTRAINTS = decltype(relation)::NUM_CONSTRAINTS; + auto expected_full_length_univariates = std::array, NUM_CONSTRAINTS>(); + + expected_full_length_univariates[0] = (q_m * w_r * w_l) + (q_r * w_r) + (q_l * w_l) + (q_o * w_o) + (q_c); + validate_evaluations(expected_full_length_univariates, relation, extended_edges, relation_parameters); }; run_test(/* is_random_input=*/true); run_test(/* is_random_input=*/false); }; -TEST_F(StandardRelationConsistency, GrandProductComputationRelation) +TEST_F(StandardRelationConsistency, PermutationRelation) { using Flavor = honk::flavor::Standard; using FF = typename Flavor::FF; @@ -210,16 +252,11 @@ TEST_F(StandardRelationConsistency, GrandProductComputationRelation) } compute_mock_extended_edges(extended_edges, input_polynomials); }; - auto relation = GrandProductComputationRelation(); + auto relation = PermutationRelation(); const auto& beta = relation_parameters.beta; const auto& gamma = relation_parameters.gamma; const auto& public_input_delta = relation_parameters.public_input_delta; - // TODO(#225)(luke): Write a test that illustrates the following? - // Note: the below z_perm_shift = X^2 will fail because it results in a relation of degree 2*1*1*1 = 5 which - // cannot be represented by 5 points. Therefore when we do the calculation then barycentrically extend, we are - // effectively exprapolating a 4th degree polynomial instead of the correct 5th degree poly - // auto z_perm_shift = Univariate({ 1, 4, 9, 16, 25 }); // X^2 // Manually compute the expected edge contribution const auto& w_1 = extended_edges.w_l; @@ -236,55 +273,19 @@ TEST_F(StandardRelationConsistency, GrandProductComputationRelation) const auto& lagrange_first = extended_edges.lagrange_first; const auto& lagrange_last = extended_edges.lagrange_last; - // We first compute the evaluations using UnivariateViews, with the provided hard-coded formula. - // Ensure that expression changes are detected. - // expected_evals in the below step { { 27, 250, 1029, 2916, 6655 } } - { { 27, 125, 343, 729, 1331 } } - auto expected_evals = (z_perm + lagrange_first) * (w_1 + id_1 * beta + gamma) * (w_2 + id_2 * beta + gamma) * - (w_3 + id_3 * beta + gamma) - - (z_perm_shift + lagrange_last * public_input_delta) * (w_1 + sigma_1 * beta + gamma) * - (w_2 + sigma_2 * beta + gamma) * (w_3 + sigma_3 * beta + gamma); + // Compute expected full length Univariates using straight forward expressions + constexpr std::size_t NUM_CONSTRAINTS = decltype(relation)::NUM_CONSTRAINTS; + auto expected_full_length_univariates = std::array, NUM_CONSTRAINTS>(); - validate_evaluations(expected_evals, relation, extended_edges, relation_parameters); - }; - run_test(/* is_random_input=*/true); - run_test(/* is_random_input=*/false); -}; + expected_full_length_univariates[0] = (z_perm + lagrange_first) * (w_1 + id_1 * beta + gamma) * + (w_2 + id_2 * beta + gamma) * (w_3 + id_3 * beta + gamma) - + (z_perm_shift + lagrange_last * public_input_delta) * + (w_1 + sigma_1 * beta + gamma) * (w_2 + sigma_2 * beta + gamma) * + (w_3 + sigma_3 * beta + gamma); -TEST_F(StandardRelationConsistency, GrandProductInitializationRelation) -{ - using Flavor = honk::flavor::Standard; - using FF = typename Flavor::FF; - static constexpr size_t FULL_RELATION_LENGTH = 5; - using ExtendedEdges = typename Flavor::template ExtendedEdges; - static const size_t NUM_POLYNOMIALS = Flavor::NUM_ALL_ENTITIES; - - const auto relation_parameters = compute_mock_relation_parameters(); - auto run_test = [&relation_parameters](bool is_random_input) { - ExtendedEdges extended_edges; - std::array, NUM_POLYNOMIALS> input_polynomials; - if (!is_random_input) { - // evaluation form, i.e. input_univariate(0) = 1, input_univariate(1) = 2,.. The polynomial is x+1. - for (size_t i = 0; i < NUM_POLYNOMIALS; ++i) { - input_polynomials[i] = Univariate({ 1, 2 }); - } - compute_mock_extended_edges(extended_edges, input_polynomials); - } else { - // input_univariates are random polynomials of degree one - for (size_t i = 0; i < NUM_POLYNOMIALS; ++i) { - input_polynomials[i] = - Univariate({ FF::random_element(), FF::random_element() }); - } - compute_mock_extended_edges(extended_edges, input_polynomials); - }; - auto relation = GrandProductInitializationRelation(); - const auto& z_perm_shift = extended_edges.z_perm_shift; - const auto& lagrange_last = extended_edges.lagrange_last; - // We first compute the evaluations using UnivariateViews, with the provided hard-coded formula. - // Ensure that expression changes are detected. - // expected_evals, lenght 3 (coeff form = x^2 + x), extends to { { 0, 2, 6, 12, 20 } } - auto expected_evals = z_perm_shift * lagrange_last; + expected_full_length_univariates[1] = z_perm_shift * lagrange_last; - validate_evaluations(expected_evals, relation, extended_edges, relation_parameters); + validate_evaluations(expected_full_length_univariates, relation, extended_edges, relation_parameters); }; run_test(/* is_random_input=*/true); run_test(/* is_random_input=*/false); diff --git a/cpp/src/barretenberg/honk/sumcheck/relations/relation_correctness.test.cpp b/cpp/src/barretenberg/honk/sumcheck/relations/relation_correctness.test.cpp index 704cdc82e9..01d3624083 100644 --- a/cpp/src/barretenberg/honk/sumcheck/relations/relation_correctness.test.cpp +++ b/cpp/src/barretenberg/honk/sumcheck/relations/relation_correctness.test.cpp @@ -3,13 +3,11 @@ #include "barretenberg/honk/composer/ultra_honk_composer.hpp" #include "barretenberg/honk/composer/standard_honk_composer.hpp" #include "barretenberg/honk/proof_system/prover_library.hpp" -#include "barretenberg/honk/sumcheck/relations/relation.hpp" +#include "barretenberg/honk/sumcheck/relations/relation_parameters.hpp" #include "barretenberg/honk/sumcheck/relations/arithmetic_relation.hpp" -#include "barretenberg/honk/sumcheck/relations/grand_product_initialization_relation.hpp" -#include "barretenberg/honk/sumcheck/relations/grand_product_computation_relation.hpp" +#include "barretenberg/honk/sumcheck/relations/permutation_relation.hpp" #include "barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation.hpp" -#include "barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation_secondary.hpp" -#include "barretenberg/honk/sumcheck/relations/lookup_grand_product_relation.hpp" +#include "barretenberg/honk/sumcheck/relations/lookup_relation.hpp" #include "barretenberg/honk/sumcheck/relations/gen_perm_sort_relation.hpp" #include "barretenberg/honk/sumcheck/relations/elliptic_relation.hpp" #include "barretenberg/honk/sumcheck/relations/auxiliary_relation.hpp" @@ -27,6 +25,40 @@ void ensure_non_zero(auto& polynomial) ASSERT_TRUE(has_non_zero_coefficient); } +/** + * @brief Check that a given relation is satified for a set of polynomials + * + * @tparam relation_idx Index into a tuple of provided relations + * @tparam Flavor + */ +template void check_relation(auto relation, auto circuit_size, auto polynomials, auto params) +{ + using ClaimedEvaluations = typename Flavor::ClaimedEvaluations; + for (size_t i = 0; i < circuit_size; i++) { + + // Extract an array containing all the polynomial evaluations at a given row i + ClaimedEvaluations evaluations_at_index_i; + size_t poly_idx = 0; + for (auto& poly : polynomials) { + evaluations_at_index_i[poly_idx] = poly[i]; + ++poly_idx; + } + + // Define the appropriate RelationValues type for this relation and initialize to zero + using RelationValues = typename decltype(relation)::RelationValues; + RelationValues result; + for (auto& element : result) { + element = 0; + } + + // Evaluate each constraint in the relation and check that each is satisfied + relation.add_full_relation_value_contribution(result, evaluations_at_index_i, params); + for (auto& element : result) { + ASSERT_EQ(element, 0); + } + } +} + /** * @brief Test the correctness of the Standard Honk relations * @@ -42,7 +74,7 @@ TEST(RelationCorrectness, StandardRelationCorrectness) using Flavor = honk::flavor::Standard; using FF = typename Flavor::FF; using ProverPolynomials = typename Flavor::ProverPolynomials; - using ClaimedEvaluations = typename Flavor::ClaimedEvaluations; + // using ClaimedEvaluations = typename Flavor::ClaimedEvaluations; // Create a composer and a dummy circuit with a few gates auto composer = StandardHonkComposer(); @@ -61,6 +93,7 @@ TEST(RelationCorrectness, StandardRelationCorrectness) } // Create a prover (it will compute proving key and witness) auto prover = composer.create_prover(); + auto circuit_size = prover.key->circuit_size; // Generate beta and gamma fr beta = fr::random_element(); @@ -107,34 +140,11 @@ TEST(RelationCorrectness, StandardRelationCorrectness) prover_polynomials.lagrange_last = prover.key->lagrange_last; // Construct the round for applying sumcheck relations and results for storing computed results - auto relations = std::tuple(honk::sumcheck::ArithmeticRelation(), - honk::sumcheck::GrandProductComputationRelation(), - honk::sumcheck::GrandProductInitializationRelation()); - - fr result = 0; - for (size_t i = 0; i < prover.key->circuit_size; i++) { - // Compute an array containing all the evaluations at a given row i - - ClaimedEvaluations evaluations_at_index_i; - size_t poly_idx = 0; - for (auto& polynomial : prover_polynomials) { - evaluations_at_index_i[poly_idx] = polynomial[i]; - ++poly_idx; - } - - // For each relation, call the `accumulate_relation_evaluation` over all witness/selector values at the - // i-th row/vertex of the hypercube. - // We use ASSERT_EQ instead of EXPECT_EQ so that the tests stops at the first index at which the result is not - // 0, since result = 0 + C(transposed), which we expect will equal 0. - std::get<0>(relations).add_full_relation_value_contribution(result, evaluations_at_index_i, params); - ASSERT_EQ(result, 0); - - std::get<1>(relations).add_full_relation_value_contribution(result, evaluations_at_index_i, params); - ASSERT_EQ(result, 0); + auto relations = std::tuple(honk::sumcheck::ArithmeticRelation(), honk::sumcheck::PermutationRelation()); - std::get<2>(relations).add_full_relation_value_contribution(result, evaluations_at_index_i, params); - ASSERT_EQ(result, 0); - } + // Check that each relation is satisfied across each row of the prover polynomials + check_relation(std::get<0>(relations), circuit_size, prover_polynomials, params); + check_relation(std::get<1>(relations), circuit_size, prover_polynomials, params); } /** @@ -154,7 +164,7 @@ TEST(RelationCorrectness, UltraRelationCorrectness) using Flavor = honk::flavor::Ultra; using FF = typename Flavor::FF; using ProverPolynomials = typename Flavor::ProverPolynomials; - using ClaimedEvaluations = typename Flavor::ClaimedEvaluations; + // using ClaimedEvaluations = typename Flavor::ClaimedEvaluations; // 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. @@ -275,6 +285,7 @@ TEST(RelationCorrectness, UltraRelationCorrectness) // Create a prover (it will compute proving key and witness) auto prover = composer.create_prover(); + auto circuit_size = prover.key->circuit_size; // Generate eta, beta and gamma fr eta = fr::random_element(); @@ -308,10 +319,6 @@ TEST(RelationCorrectness, UltraRelationCorrectness) // Compute lookup grand product polynomial prover.key->z_lookup = prover_library::compute_lookup_grand_product(prover.key, eta, beta, gamma); - // Create an array of spans to the underlying polynomials to more easily - // get the transposition. - // Ex: polynomial_spans[3][i] returns the i-th coefficient of the third polynomial - // in the list below ProverPolynomials prover_polynomials; prover_polynomials.w_l = prover.key->w_l; @@ -367,56 +374,19 @@ TEST(RelationCorrectness, UltraRelationCorrectness) // Construct the round for applying sumcheck relations and results for storing computed results auto relations = std::tuple(honk::sumcheck::UltraArithmeticRelation(), - honk::sumcheck::UltraArithmeticRelationSecondary(), - honk::sumcheck::UltraGrandProductInitializationRelation(), - honk::sumcheck::UltraGrandProductComputationRelation(), - honk::sumcheck::LookupGrandProductComputationRelation(), - honk::sumcheck::LookupGrandProductInitializationRelation(), + honk::sumcheck::UltraPermutationRelation(), + honk::sumcheck::LookupRelation(), honk::sumcheck::GenPermSortRelation(), honk::sumcheck::EllipticRelation(), honk::sumcheck::AuxiliaryRelation()); - fr result = 0; - for (size_t i = 0; i < prover.key->circuit_size; i++) { - // Compute an array containing all the evaluations at a given row i - ClaimedEvaluations evaluations_at_index_i; - size_t poly_idx = 0; - for (auto& polynomial : prover_polynomials) { - evaluations_at_index_i[poly_idx] = polynomial[i]; - ++poly_idx; - } - - // For each relation, call the `accumulate_relation_evaluation` over all witness/selector values at the - // i-th row/vertex of the hypercube. We use ASSERT_EQ instead of EXPECT_EQ so that the tests stops at - // the first index at which the result is not 0, since result = 0 + C(transposed), which we expect will - // equal 0. - std::get<0>(relations).add_full_relation_value_contribution(result, evaluations_at_index_i, params); - ASSERT_EQ(result, 0); - - std::get<1>(relations).add_full_relation_value_contribution(result, evaluations_at_index_i, params); - ASSERT_EQ(result, 0); - - std::get<2>(relations).add_full_relation_value_contribution(result, evaluations_at_index_i, params); - ASSERT_EQ(result, 0); - - std::get<3>(relations).add_full_relation_value_contribution(result, evaluations_at_index_i, params); - ASSERT_EQ(result, 0); - - std::get<4>(relations).add_full_relation_value_contribution(result, evaluations_at_index_i, params); - ASSERT_EQ(result, 0); - - std::get<5>(relations).add_full_relation_value_contribution(result, evaluations_at_index_i, params); - ASSERT_EQ(result, 0); - - std::get<6>(relations).add_full_relation_value_contribution(result, evaluations_at_index_i, params); - ASSERT_EQ(result, 0); - - std::get<7>(relations).add_full_relation_value_contribution(result, evaluations_at_index_i, params); - ASSERT_EQ(result, 0); - - std::get<8>(relations).add_full_relation_value_contribution(result, evaluations_at_index_i, params); - ASSERT_EQ(result, 0); - } + // Check that each relation is satisfied across each row of the prover polynomials + check_relation(std::get<0>(relations), circuit_size, prover_polynomials, params); + check_relation(std::get<1>(relations), circuit_size, prover_polynomials, params); + check_relation(std::get<2>(relations), circuit_size, prover_polynomials, params); + check_relation(std::get<3>(relations), circuit_size, prover_polynomials, params); + check_relation(std::get<4>(relations), circuit_size, prover_polynomials, params); + check_relation(std::get<5>(relations), circuit_size, prover_polynomials, params); } } // namespace test_honk_relations diff --git a/cpp/src/barretenberg/honk/sumcheck/relations/relation.hpp b/cpp/src/barretenberg/honk/sumcheck/relations/relation_parameters.hpp similarity index 100% rename from cpp/src/barretenberg/honk/sumcheck/relations/relation.hpp rename to cpp/src/barretenberg/honk/sumcheck/relations/relation_parameters.hpp diff --git a/cpp/src/barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation.hpp b/cpp/src/barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation.hpp index dd5aa02b85..1ea1de263e 100644 --- a/cpp/src/barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation.hpp +++ b/cpp/src/barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation.hpp @@ -3,7 +3,7 @@ #include #include "../polynomials/univariate.hpp" -#include "relation.hpp" +#include "relation_parameters.hpp" namespace proof_system::honk::sumcheck { @@ -12,55 +12,127 @@ template class UltraArithmeticRelation { // 1 + polynomial degree of this relation static constexpr size_t RELATION_LENGTH = 6; // degree(q_arith^2 * q_m * w_r * w_l) = 5 + static constexpr size_t NUM_CONSTRAINTS = 2; + // TODO(luke): The degree of the identities varies based on q_arith. Can we do something to improve efficiency here? + static constexpr std::array CONSTRAINT_LENGTH = { 6, 5 }; + + using RelationUnivariates = std::tuple, Univariate>; + using RelationValues = std::array; + /** * @brief Expression for the Ultra Arithmetic gate. - * @details The relation is defined as C(extended_edges(X)...) = + * @details This relation encapsulates several idenitities, toggled by the value of q_arith in [0, 1, 2, 3, ...]. + * The following description is reproduced from the Plonk analog 'plookup_arithmetic_widget': + * The whole formula is: + * + * q_arith * ( ( (-1/2) * (q_arith - 3) * q_m * w_1 * w_2 + q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c ) + + * (q_arith - 1)*( α * (q_arith - 2) * (w_1 + w_4 - w_1_omega + q_m) + w_4_omega) ) = 0 + * + * This formula results in several cases depending on q_arith: + * 1. q_arith == 0: Arithmetic gate is completely disabled + * + * 2. q_arith == 1: Everything in the minigate on the right is disabled. The equation is just a standard plonk + * equation with extra wires: q_m * w_1 * w_2 + q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c = 0 + * + * 3. q_arith == 2: The (w_1 + w_4 - ...) term is disabled. THe equation is: + * (1/2) * q_m * w_1 * w_2 + q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c + w_4_omega = 0 + * It allows defining w_4 at next index (w_4_omega) in terms of current wire values + * + * 4. q_arith == 3: The product of w_1 and w_2 is disabled, but a mini addition gate is enabled. α² allows us to + * split the equation into two: + * + * q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c + 2 * w_4_omega = 0 + * + * w_1 + w_4 - w_1_omega + q_m = 0 (we are reusing q_m here) + * + * 5. q_arith > 3: The product of w_1 and w_2 is scaled by (q_arith - 3), while the w_4_omega term is scaled by + * (q_arith + * - 1). The equation can be split into two: + * + * (q_arith - 3)* q_m * w_1 * w_ 2 + q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c + (q_arith - 1) * w_4_omega + * = 0 + * + * w_1 + w_4 - w_1_omega + q_m = 0 + * + * The problem that q_m is used both in both equations can be dealt with by appropriately changing selector values + * at the next gate. Then we can treat (q_arith - 1) as a simulated q_6 selector and scale q_m to handle (q_arith - + * 3) at product. + * + * The The relation is + * defined as C(extended_edges(X)...) = q_arith * [ -1/2(q_arith - 3)(q_m * w_r * w_l) + (q_l * w_l) + (q_r * w_r) + + * (q_o * w_o) + (q_4 * w_4) + q_c + (q_arith - 1)w_4_shift ] + * * q_arith * - * [ -1/2(q_arith - 3)(q_m * w_r * w_l) + - * (q_l * w_l) + (q_r * w_r) + (q_o * w_o) + (q_4 * w_4) + q_c + - * (q_arith - 1)w_4_shift ] + * (q_arith - 2) * (q_arith - 1) * (w_l + w_4 - w_l_shift + q_m) * * @param evals transformed to `evals + C(extended_edges(X)...)*scaling_factor` * @param extended_edges an std::array containing the fully extended Univariate edges. * @param parameters contains beta, gamma, and public_input_delta, .... * @param scaling_factor optional term to scale the evaluation before adding to evals. */ - void add_edge_contribution(Univariate& evals, + void add_edge_contribution(RelationUnivariates& evals, const auto& extended_edges, const RelationParameters&, - const FF& scaling_factor) const - { + const FF& scaling_factor) const { // OPTIMIZATION?: Karatsuba in general, at least for some degrees? // See https://hackmd.io/xGLuj6biSsCjzQnYN-pEiA?both + // clang-format off + // Contribution 1 + { + static constexpr size_t LENGTH = CONSTRAINT_LENGTH[0]; + auto w_l = UnivariateView(extended_edges.w_l); + auto w_r = UnivariateView(extended_edges.w_r); + auto w_o = UnivariateView(extended_edges.w_o); + auto w_4 = UnivariateView(extended_edges.w_4); + auto w_4_shift = UnivariateView(extended_edges.w_4_shift); + auto q_m = UnivariateView(extended_edges.q_m); + auto q_l = UnivariateView(extended_edges.q_l); + auto q_r = UnivariateView(extended_edges.q_r); + auto q_o = UnivariateView(extended_edges.q_o); + auto q_4 = UnivariateView(extended_edges.q_4); + auto q_c = UnivariateView(extended_edges.q_c); + auto q_arith = UnivariateView(extended_edges.q_arith); - auto w_l = UnivariateView(extended_edges.w_l); - auto w_r = UnivariateView(extended_edges.w_r); - auto w_o = UnivariateView(extended_edges.w_o); - auto w_4 = UnivariateView(extended_edges.w_4); - auto w_4_shift = UnivariateView(extended_edges.w_4_shift); - auto q_m = UnivariateView(extended_edges.q_m); - auto q_l = UnivariateView(extended_edges.q_l); - auto q_r = UnivariateView(extended_edges.q_r); - auto q_o = UnivariateView(extended_edges.q_o); - auto q_4 = UnivariateView(extended_edges.q_4); - auto q_c = UnivariateView(extended_edges.q_c); - auto q_arith = UnivariateView(extended_edges.q_arith); + static const FF neg_half = FF(-2).invert(); - static const FF neg_half = FF(-2).invert(); + auto tmp = (q_arith - 3) * (q_m * w_r * w_l) * neg_half; + tmp += (q_l * w_l) + (q_r * w_r) + (q_o * w_o) + (q_4 * w_4) + q_c; + tmp += (q_arith - 1) * w_4_shift; + tmp *= q_arith; + tmp *= scaling_factor; + std::get<0>(evals) += tmp; + } + // Contribution 2 + { + static constexpr size_t LENGTH = CONSTRAINT_LENGTH[1]; + auto w_l = UnivariateView(extended_edges.w_l); + auto w_4 = UnivariateView(extended_edges.w_4); + auto w_l_shift = UnivariateView(extended_edges.w_l_shift); + auto q_m = UnivariateView(extended_edges.q_m); + auto q_arith = UnivariateView(extended_edges.q_arith); - auto tmp = (q_arith - 3) * (q_m * w_r * w_l) * neg_half; - tmp += (q_l * w_l) + (q_r * w_r) + (q_o * w_o) + (q_4 * w_4) + q_c; - tmp += (q_arith - 1) * w_4_shift; - tmp *= q_arith; - tmp *= scaling_factor; - evals += tmp; - }; + auto tmp = w_l + w_4 - w_l_shift + q_m; + tmp *= (q_arith - 2); + tmp *= (q_arith - 1); + tmp *= q_arith; + tmp *= scaling_factor; + std::get<1>(evals) += tmp; + } + }; // namespace proof_system::honk::sumcheck - void add_full_relation_value_contribution(FF& full_honk_relation_value, - const auto& purported_evaluations, - const RelationParameters&) const + /** + * @brief Add the result of each identity in this relation evaluated at the multivariate evaluations produced by the + * Sumcheck Prover. + * + * @param full_honk_relation_value + * @param purported_evaluations + */ + void add_full_relation_value_contribution(RelationValues& full_honk_relation_value, + const auto& purported_evaluations, + const RelationParameters&) const { auto w_l = purported_evaluations.w_l; + auto w_l_shift = purported_evaluations.w_l_shift; auto w_r = purported_evaluations.w_r; auto w_o = purported_evaluations.w_o; auto w_4 = purported_evaluations.w_4; @@ -75,11 +147,20 @@ template class UltraArithmeticRelation { static const FF neg_half = FF(-2).invert(); + // Contribution 1 auto tmp = (q_arith - 3) * (q_m * w_r * w_l) * neg_half; tmp += (q_l * w_l) + (q_r * w_r) + (q_o * w_o) + (q_4 * w_4) + q_c; tmp += (q_arith - 1) * w_4_shift; tmp *= q_arith; - full_honk_relation_value += tmp; + std::get<0>(full_honk_relation_value) += tmp; + + // Contribution 2 + tmp = w_l + w_4 - w_l_shift + q_m; + tmp *= (q_arith - 2); + tmp *= (q_arith - 1); + tmp *= q_arith; + std::get<1>(full_honk_relation_value) += tmp; }; }; +// clang-format on } // namespace proof_system::honk::sumcheck diff --git a/cpp/src/barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation_secondary.hpp b/cpp/src/barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation_secondary.hpp deleted file mode 100644 index 84bb0ce60c..0000000000 --- a/cpp/src/barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation_secondary.hpp +++ /dev/null @@ -1,66 +0,0 @@ -#pragma once -#include -#include - -#include "../polynomials/univariate.hpp" -#include "relation.hpp" - -// TODO(luke): Move this into ultra_arithmetic_relation.hpp. -namespace proof_system::honk::sumcheck { - -template class UltraArithmeticRelationSecondary { - public: - // 1 + polynomial degree of this relation - static constexpr size_t RELATION_LENGTH = 5; // degree(q_arith^3 * w_l) = 4 - - /** - * @brief Expression for the Ultra Arithmetic gate. - * @details The relation is defined as C(extended_edges(X)...) = - * q_arith * - * (q_arith - 2) * (q_arith - 1) * (w_l + w_4 - w_l_shift + q_m) - * - * @param evals transformed to `evals + C(extended_edges(X)...)*scaling_factor` - * @param extended_edges an std::array containing the fully extended Univariate edges. - * @param parameters contains beta, gamma, and public_input_delta, .... - * @param scaling_factor optional term to scale the evaluation before adding to evals. - */ - void add_edge_contribution(Univariate& evals, - const auto& extended_edges, - const RelationParameters&, - const FF& scaling_factor) const - { - // OPTIMIZATION?: Karatsuba in general, at least for some degrees? - // See https://hackmd.io/xGLuj6biSsCjzQnYN-pEiA?both - - auto w_l = UnivariateView(extended_edges.w_l); - auto w_4 = UnivariateView(extended_edges.w_4); - auto w_l_shift = UnivariateView(extended_edges.w_l_shift); - auto q_m = UnivariateView(extended_edges.q_m); - auto q_arith = UnivariateView(extended_edges.q_arith); - - auto tmp = w_l + w_4 - w_l_shift + q_m; - tmp *= (q_arith - 2); - tmp *= (q_arith - 1); - tmp *= q_arith; - tmp *= scaling_factor; - evals += tmp; - }; - - void add_full_relation_value_contribution(FF& full_honk_relation_value, - const auto& purported_evaluations, - const RelationParameters&) const - { - auto w_l = purported_evaluations.w_l; - auto w_4 = purported_evaluations.w_4; - auto w_l_shift = purported_evaluations.w_l_shift; - auto q_m = purported_evaluations.q_m; - auto q_arith = purported_evaluations.q_arith; - - auto tmp = w_l + w_4 - w_l_shift + q_m; - tmp *= (q_arith - 2); - tmp *= (q_arith - 1); - tmp *= q_arith; - full_honk_relation_value += tmp; - }; -}; -} // namespace proof_system::honk::sumcheck diff --git a/cpp/src/barretenberg/honk/sumcheck/relations/ultra_relation_consistency.test.cpp b/cpp/src/barretenberg/honk/sumcheck/relations/ultra_relation_consistency.test.cpp index ce44727e48..1be27d4bda 100644 --- a/cpp/src/barretenberg/honk/sumcheck/relations/ultra_relation_consistency.test.cpp +++ b/cpp/src/barretenberg/honk/sumcheck/relations/ultra_relation_consistency.test.cpp @@ -1,14 +1,13 @@ #include "barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation.hpp" -#include "barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation_secondary.hpp" -#include "barretenberg/honk/sumcheck/relations/lookup_grand_product_relation.hpp" +#include "barretenberg/honk/sumcheck/relations/permutation_relation.hpp" +#include "barretenberg/honk/sumcheck/relations/lookup_relation.hpp" #include "barretenberg/honk/sumcheck/relations/gen_perm_sort_relation.hpp" #include "barretenberg/honk/sumcheck/relations/elliptic_relation.hpp" #include "barretenberg/honk/sumcheck/relations/auxiliary_relation.hpp" #include "barretenberg/honk/flavor/ultra.hpp" -#include "relation.hpp" +#include "relation_parameters.hpp" #include "arithmetic_relation.hpp" -#include "grand_product_initialization_relation.hpp" -#include "grand_product_computation_relation.hpp" +#include "permutation_relation.hpp" #include "../polynomials/univariate.hpp" #include "../polynomials/barycentric_data.hpp" @@ -17,16 +16,18 @@ #include #include +// TODO(luke): This testing infrastructure was duplicated between here and relation_consistency.test.cpp with the +// orignal Flavor PR. Find a way to recombine these test suites or at least share this functionality. using namespace proof_system::honk::sumcheck; /** - * We want to test if all three relations (namely, ArithmeticRelation, GrandProductComputationRelation, - * GrandProductInitializationRelation) provide correct contributions by manually computing their - * contributions with deterministic and random inputs. The relations are supposed to work with - * univariates (edges) of degree one (length 2) and spit out polynomials of corresponding degrees. We have - * MAX_RELATION_LENGTH = 5, meaning the output of a relation can atmost be a degree 5 polynomial. Hence, - * we use a method compute_mock_extended_edges() which starts with degree one input polynomial (two evaluation - points), - * extends them (using barycentric formula) to six evaluation points, and stores them to an array of polynomials. + * The purpose of this test suite is to show that the identity arithmetic implemented in the Relations is equivalent to + * a simpler unoptimized version implemented in the tests themselves. This is useful 1) as documentation since the + * simple implementations here should make the underlying arithmetic easier to see, and 2) as a check that optimizations + * introduced into the Relations have not changed the result. + * + * For this purpose, we simply feed (the same) random inputs into each of the two implementations and confirm that + * the outputs match. This does not confirm the correctness of the identity arithmetic (the identities will not be + * satisfied in general by random inputs) only that the two implementations are equivalent. */ static const size_t INPUT_UNIVARIATE_LENGTH = 2; @@ -63,9 +64,11 @@ class UltraRelationConsistency : public testing::Test { */ RelationParameters compute_mock_relation_parameters() { - return { .beta = FF::random_element(), + return { .eta = FF::random_element(), + .beta = FF::random_element(), .gamma = FF::random_element(), - .public_input_delta = FF::random_element() }; + .public_input_delta = FF::random_element(), + .lookup_grand_product_delta = FF::random_element() }; } /** @@ -108,34 +111,76 @@ class UltraRelationConsistency : public testing::Test { * @param relation_parameters */ template - static void validate_evaluations(const Univariate& expected_evals, + static void validate_evaluations(const auto& expected_full_length_univariates, /* array of Univariates*/ const auto relation, const ExtendedEdges& extended_edges, const RelationParameters& relation_parameters) { - - // Compute the expression index-by-index - Univariate expected_evals_index{ 0 }; - for (size_t i = 0; i < FULL_RELATION_LENGTH; ++i) { - // Get an array of the same size as `extended_edges` with only the i-th element of each extended edge. - ClaimedEvaluations evals_i = transposed_univariate_array_at(extended_edges, i); - // Evaluate the relation - relation.add_full_relation_value_contribution( - expected_evals_index.value_at(i), evals_i, relation_parameters); + // First check that the verifier's computation on individual evaluations is correct. + // Note: since add_full_relation_value_contribution computes the identities at a single evaluation of the + // multivariates, we need only pass in one evaluation point from the extended edges. Which one we choose is + // arbitrary so we choose the 0th. + + // Extract the RelationValues type for the given relation + using RelationValues = typename decltype(relation)::RelationValues; + RelationValues relation_evals; + RelationValues expected_relation_evals; + + ASSERT_EQ(expected_relation_evals.size(), expected_full_length_univariates.size()); + // Initialize expected_evals to 0th coefficient of expected full length univariates + for (size_t idx = 0; idx < relation_evals.size(); ++idx) { + relation_evals[idx] = FF(0); // initialize to 0 + expected_relation_evals[idx] = expected_full_length_univariates[idx].value_at(0); } - EXPECT_EQ(expected_evals, expected_evals_index); - - // Compute the expression using the class, that converts the extended edges to UnivariateView - auto expected_evals_view = Univariate(0); - // The scaling factor is essentially 1 since we are working with degree 1 univariates - relation.add_edge_contribution(expected_evals_view, extended_edges, relation_parameters, 1); - - // Tiny hack to reduce `expected_evals` to be of size `relation.RELATION_LENGTH` - Univariate expected_evals_restricted{ - UnivariateView(expected_evals) - }; - EXPECT_EQ(expected_evals_restricted, expected_evals_view); + + // Extract 0th evaluation from extended edges + ClaimedEvaluations edge_evaluations = transposed_univariate_array_at(extended_edges, 0); + + // Evaluate the relation using the verifier functionality + relation.add_full_relation_value_contribution(relation_evals, edge_evaluations, relation_parameters); + + EXPECT_EQ(relation_evals, expected_relation_evals); + + // Next, check that the prover's computation on Univariates is correct + + using RelationUnivariates = typename decltype(relation)::RelationUnivariates; + RelationUnivariates relation_univariates; + zero_univariates<>(relation_univariates); + + constexpr std::size_t num_univariates = std::tuple_size::value; + + // Compute the relatiion univariates via the sumcheck prover functionality, then extend + // them to full length for easy comparison with the expected result. + relation.add_edge_contribution(relation_univariates, extended_edges, relation_parameters, 1); + + auto full_length_univariates = std::array, num_univariates>(); + extend_tuple_of_arrays(relation_univariates, full_length_univariates); + + EXPECT_EQ(full_length_univariates, expected_full_length_univariates); }; + + template static void zero_univariates(std::tuple& tuple) + { + auto& element = std::get(tuple); + std::fill(element.evaluations.begin(), element.evaluations.end(), FF(0)); + + if constexpr (idx + 1 < sizeof...(Ts)) { + zero_univariates(tuple); + } + } + + template + static void extend_tuple_of_arrays(std::tuple& tuple, auto& result_univariates) + { + auto& element = std::get(tuple); + using Element = std::remove_reference_t; + BarycentricData barycentric_utils; + result_univariates[idx] = barycentric_utils.extend(element); + + if constexpr (idx + 1 < sizeof...(Ts)) { + extend_tuple_of_arrays(tuple, result_univariates); + } + } }; TEST_F(UltraRelationConsistency, UltraArithmeticRelation) @@ -160,6 +205,7 @@ TEST_F(UltraRelationConsistency, UltraArithmeticRelation) // Extract the extended edges for manual computation of relation contribution const auto& w_1 = extended_edges.w_l; + const auto& w_1_shift = extended_edges.w_l_shift; const auto& w_2 = extended_edges.w_r; const auto& w_3 = extended_edges.w_o; const auto& w_4 = extended_edges.w_4; @@ -174,78 +220,25 @@ TEST_F(UltraRelationConsistency, UltraArithmeticRelation) static const FF neg_half = FF(-2).invert(); - auto expected_evals = (q_arith - 3) * (q_m * w_2 * w_1) * neg_half; - expected_evals += (q_l * w_1) + (q_r * w_2) + (q_o * w_3) + (q_4 * w_4) + q_c; - expected_evals += (q_arith - 1) * w_4_shift; - expected_evals *= q_arith; - - validate_evaluations(expected_evals, relation, extended_edges, relation_parameters); -}; - -TEST_F(UltraRelationConsistency, UltraArithmeticRelationSecondary) -{ - using Flavor = honk::flavor::Ultra; - using FF = typename Flavor::FF; - static constexpr size_t FULL_RELATION_LENGTH = 6; - using ExtendedEdges = typename Flavor::template ExtendedEdges; - static const size_t NUM_POLYNOMIALS = Flavor::NUM_ALL_ENTITIES; - - const auto relation_parameters = compute_mock_relation_parameters(); - ExtendedEdges extended_edges; - std::array, NUM_POLYNOMIALS> input_polynomials; - - // input_univariates are random polynomials of degree one - for (size_t i = 0; i < NUM_POLYNOMIALS; ++i) { - input_polynomials[i] = Univariate({ FF::random_element(), FF::random_element() }); - } - compute_mock_extended_edges(extended_edges, input_polynomials); - - auto relation = UltraArithmeticRelationSecondary(); + constexpr std::size_t NUM_CONSTRAINTS = decltype(relation)::NUM_CONSTRAINTS; + auto expected_full_length_univariates = std::array, NUM_CONSTRAINTS>(); - // Extract the extended edges for manual computation of relation contribution - const auto& w_1 = extended_edges.w_l; - const auto& w_4 = extended_edges.w_4; - const auto& w_l_shift = extended_edges.w_l_shift; - const auto& q_m = extended_edges.q_m; - const auto& q_arith = extended_edges.q_arith; + // Contribution 1 + auto contribution_1 = (q_arith - 3) * (q_m * w_2 * w_1) * neg_half; + contribution_1 += (q_l * w_1) + (q_r * w_2) + (q_o * w_3) + (q_4 * w_4) + q_c; + contribution_1 += (q_arith - 1) * w_4_shift; + contribution_1 *= q_arith; + expected_full_length_univariates[0] = contribution_1; - auto expected_evals = (w_1 + w_4 - w_l_shift + q_m); - expected_evals *= (q_arith - 2) * (q_arith - 1) * q_arith; + // Contribution 2 + auto contribution_2 = (w_1 + w_4 - w_1_shift + q_m); + contribution_2 *= (q_arith - 2) * (q_arith - 1) * q_arith; + expected_full_length_univariates[1] = contribution_2; - validate_evaluations(expected_evals, relation, extended_edges, relation_parameters); + validate_evaluations(expected_full_length_univariates, relation, extended_edges, relation_parameters); }; -TEST_F(UltraRelationConsistency, UltraGrandProductInitializationRelation) -{ - using Flavor = honk::flavor::Ultra; - using FF = typename Flavor::FF; - using Flavor = honk::flavor::Ultra; - static constexpr size_t FULL_RELATION_LENGTH = 6; - using ExtendedEdges = typename Flavor::ExtendedEdges; - static const size_t NUM_POLYNOMIALS = Flavor::NUM_ALL_ENTITIES; - auto relation_parameters = compute_mock_relation_parameters(); - ExtendedEdges extended_edges; - std::array, NUM_POLYNOMIALS> input_polynomials; - - // input_univariates are random polynomials of degree one - for (size_t i = 0; i < NUM_POLYNOMIALS; ++i) { - input_polynomials[i] = Univariate({ FF::random_element(), FF::random_element() }); - } - compute_mock_extended_edges(extended_edges, input_polynomials); - - auto relation = UltraGrandProductInitializationRelation(); - - // Extract the extended edges for manual computation of relation contribution - const auto& z_perm_shift = extended_edges.z_perm_shift; - const auto& lagrange_last = extended_edges.lagrange_last; - - // Compute the expected result using a simple to read version of the relation expression - auto expected_evals = z_perm_shift * lagrange_last; - - validate_evaluations(expected_evals, relation, extended_edges, relation_parameters); -}; - -TEST_F(UltraRelationConsistency, UltraGrandProductComputationRelation) +TEST_F(UltraRelationConsistency, UltraPermutationRelation) { using Flavor = honk::flavor::Ultra; using FF = typename Flavor::FF; @@ -263,7 +256,7 @@ TEST_F(UltraRelationConsistency, UltraGrandProductComputationRelation) } compute_mock_extended_edges(extended_edges, input_polynomials); - auto relation = UltraGrandProductComputationRelation(); + auto relation = UltraPermutationRelation(); const auto& beta = relation_parameters.beta; const auto& gamma = relation_parameters.gamma; @@ -287,17 +280,27 @@ TEST_F(UltraRelationConsistency, UltraGrandProductComputationRelation) const auto& lagrange_first = extended_edges.lagrange_first; const auto& lagrange_last = extended_edges.lagrange_last; + constexpr std::size_t NUM_CONSTRAINTS = decltype(relation)::NUM_CONSTRAINTS; + auto expected_full_length_univariates = std::array, NUM_CONSTRAINTS>(); + // Compute the expected result using a simple to read version of the relation expression - auto expected_evals = (z_perm + lagrange_first) * (w_1 + id_1 * beta + gamma) * (w_2 + id_2 * beta + gamma) * + + // Contribution 1 + auto contribution_1 = (z_perm + lagrange_first) * (w_1 + id_1 * beta + gamma) * (w_2 + id_2 * beta + gamma) * (w_3 + id_3 * beta + gamma) * (w_4 + id_4 * beta + gamma) - (z_perm_shift + lagrange_last * public_input_delta) * (w_1 + sigma_1 * beta + gamma) * (w_2 + sigma_2 * beta + gamma) * (w_3 + sigma_3 * beta + gamma) * (w_4 + sigma_4 * beta + gamma); + expected_full_length_univariates[0] = contribution_1; - validate_evaluations(expected_evals, relation, extended_edges, relation_parameters); + // Contribution 2 + auto contribution_2 = z_perm_shift * lagrange_last; + expected_full_length_univariates[1] = contribution_2; + + validate_evaluations(expected_full_length_univariates, relation, extended_edges, relation_parameters); }; -TEST_F(UltraRelationConsistency, LookupGrandProductComputationRelation) +TEST_F(UltraRelationConsistency, LookupRelation) { using Flavor = honk::flavor::Ultra; using FF = typename Flavor::FF; @@ -315,7 +318,7 @@ TEST_F(UltraRelationConsistency, LookupGrandProductComputationRelation) } compute_mock_extended_edges(extended_edges, input_polynomials); - auto relation = LookupGrandProductComputationRelation(); + auto relation = LookupRelation(); const auto eta = relation_parameters.eta; const auto beta = relation_parameters.beta; @@ -366,43 +369,23 @@ TEST_F(UltraRelationConsistency, LookupGrandProductComputationRelation) auto table_accum = table_1 + table_2 * eta + table_3 * eta_sqr + table_4 * eta_cube; auto table_accum_shift = table_1_shift + table_2_shift * eta + table_3_shift * eta_sqr + table_4_shift * eta_cube; + constexpr std::size_t NUM_CONSTRAINTS = decltype(relation)::NUM_CONSTRAINTS; + auto expected_full_length_univariates = std::array, NUM_CONSTRAINTS>(); + // Compute the expected result using a simple to read version of the relation expression - auto expected_evals = (z_lookup + lagrange_first) * (q_lookup * wire_accum + gamma) * + + // Contribution 1 + auto contribution_1 = (z_lookup + lagrange_first) * (q_lookup * wire_accum + gamma) * (table_accum + table_accum_shift * beta + gamma_by_one_plus_beta) * one_plus_beta; - expected_evals -= (z_lookup_shift + lagrange_last * grand_product_delta) * + contribution_1 -= (z_lookup_shift + lagrange_last * grand_product_delta) * (s_accum + s_accum_shift * beta + gamma_by_one_plus_beta); + expected_full_length_univariates[0] = contribution_1; - validate_evaluations(expected_evals, relation, extended_edges, relation_parameters); -}; + // Contribution 2 + auto contribution_2 = z_lookup_shift * lagrange_last; + expected_full_length_univariates[1] = contribution_2; -TEST_F(UltraRelationConsistency, LookupGrandProductInitializationRelation) -{ - using Flavor = honk::flavor::Ultra; - using FF = typename Flavor::FF; - using Flavor = honk::flavor::Ultra; - static constexpr size_t FULL_RELATION_LENGTH = 6; - using ExtendedEdges = typename Flavor::ExtendedEdges; - static const size_t NUM_POLYNOMIALS = Flavor::NUM_ALL_ENTITIES; - auto relation_parameters = compute_mock_relation_parameters(); - ExtendedEdges extended_edges; - std::array, NUM_POLYNOMIALS> input_polynomials; - - // input_univariates are random polynomials of degree one - for (size_t i = 0; i < NUM_POLYNOMIALS; ++i) { - input_polynomials[i] = Univariate({ FF::random_element(), FF::random_element() }); - } - compute_mock_extended_edges(extended_edges, input_polynomials); - - auto relation = LookupGrandProductInitializationRelation(); - - // Extract the extended edges for manual computation of relation contribution - const auto& z_lookup_shift = extended_edges.z_lookup_shift; - const auto& lagrange_last = extended_edges.lagrange_last; - - // Compute the expected result using a simple to read version of the relation expression - auto expected_evals = z_lookup_shift * lagrange_last; - - validate_evaluations(expected_evals, relation, extended_edges, relation_parameters); + validate_evaluations(expected_full_length_univariates, relation, extended_edges, relation_parameters); }; TEST_F(UltraRelationConsistency, GenPermSortRelation) @@ -436,25 +419,27 @@ TEST_F(UltraRelationConsistency, GenPermSortRelation) const auto& w_1_shift = extended_edges.w_l_shift; const auto& q_sort = extended_edges.q_sort; - static const FF fake_alpha_1 = FF(1); - static const FF fake_alpha_2 = fake_alpha_1 * fake_alpha_1; - static const FF fake_alpha_3 = fake_alpha_2 * fake_alpha_1; - static const FF fake_alpha_4 = fake_alpha_3 * fake_alpha_1; - // Compute wire differences auto delta_1 = w_2 - w_1; auto delta_2 = w_3 - w_2; auto delta_3 = w_4 - w_3; auto delta_4 = w_1_shift - w_4; + constexpr std::size_t NUM_CONSTRAINTS = decltype(relation)::NUM_CONSTRAINTS; + auto expected_full_length_univariates = std::array, NUM_CONSTRAINTS>(); + // Compute the expected result using a simple to read version of the relation expression - auto expected_evals = delta_1 * (delta_1 - 1) * (delta_1 - 2) * (delta_1 - 3) * fake_alpha_1; - expected_evals += delta_2 * (delta_2 - 1) * (delta_2 - 2) * (delta_2 - 3) * fake_alpha_2; - expected_evals += delta_3 * (delta_3 - 1) * (delta_3 - 2) * (delta_3 - 3) * fake_alpha_3; - expected_evals += delta_4 * (delta_4 - 1) * (delta_4 - 2) * (delta_4 - 3) * fake_alpha_4; - expected_evals *= q_sort; + auto contribution_1 = delta_1 * (delta_1 - 1) * (delta_1 - 2) * (delta_1 - 3); + auto contribution_2 = delta_2 * (delta_2 - 1) * (delta_2 - 2) * (delta_2 - 3); + auto contribution_3 = delta_3 * (delta_3 - 1) * (delta_3 - 2) * (delta_3 - 3); + auto contribution_4 = delta_4 * (delta_4 - 1) * (delta_4 - 2) * (delta_4 - 3); + + expected_full_length_univariates[0] = contribution_1 * q_sort; + expected_full_length_univariates[1] = contribution_2 * q_sort; + expected_full_length_univariates[2] = contribution_3 * q_sort; + expected_full_length_univariates[3] = contribution_4 * q_sort; - validate_evaluations(expected_evals, relation, extended_edges, relation_parameters); + validate_evaluations(expected_full_length_univariates, relation, extended_edges, relation_parameters); }; TEST_F(UltraRelationConsistency, EllipticRelation) @@ -491,23 +476,26 @@ TEST_F(UltraRelationConsistency, EllipticRelation) const auto& q_beta_sqr = extended_edges.q_4; const auto& q_elliptic = extended_edges.q_elliptic; - static const FF fake_alpha_1 = FF(1); - static const FF fake_alpha_2 = fake_alpha_1 * fake_alpha_1; + constexpr std::size_t NUM_CONSTRAINTS = decltype(relation)::NUM_CONSTRAINTS; + auto expected_full_length_univariates = std::array, NUM_CONSTRAINTS>(); // Compute x/y coordinate identities + + // Contribution 1 auto x_identity = q_sign * (y_1 * y_2 * 2); x_identity += q_beta * (x_1 * x_2 * x_3 * 2 + x_1 * x_1 * x_2) * FF(-1); x_identity += q_beta_sqr * (x_2 * x_2 * x_3 - x_1 * x_2 * x_2); x_identity += (x_1 * x_1 * x_3 - y_2 * y_2 - y_1 * y_1 + x_2 * x_2 * x_2 + x_1 * x_1 * x_1); + // Contribution 2 auto y_identity = q_sign * (y_2 * x_3 - y_2 * x_1); y_identity += q_beta * (x_2 * y_3 + y_1 * x_2); y_identity += (x_1 * y_1 - x_1 * y_3 - y_1 * x_3 - x_1 * y_1); - auto expected_evals = x_identity * fake_alpha_1 + y_identity * fake_alpha_2; - expected_evals *= q_elliptic; + expected_full_length_univariates[0] = x_identity * q_elliptic; + expected_full_length_univariates[1] = y_identity * q_elliptic; - validate_evaluations(expected_evals, relation, extended_edges, relation_parameters); + validate_evaluations(expected_full_length_univariates, relation, extended_edges, relation_parameters); }; TEST_F(UltraRelationConsistency, AuxiliaryRelation) @@ -531,7 +519,6 @@ TEST_F(UltraRelationConsistency, AuxiliaryRelation) auto relation = AuxiliaryRelation(); const auto& eta = relation_parameters.eta; - const auto fake_alpha = FF(1); // Extract the extended edges for manual computation of relation contribution const auto& w_1 = extended_edges.w_l; @@ -552,6 +539,9 @@ TEST_F(UltraRelationConsistency, AuxiliaryRelation) const auto& q_arith = extended_edges.q_arith; const auto& q_aux = extended_edges.q_aux; + constexpr std::size_t NUM_CONSTRAINTS = decltype(relation)::NUM_CONSTRAINTS; + auto expected_full_length_univariates = std::array, NUM_CONSTRAINTS>(); + constexpr FF LIMB_SIZE(uint256_t(1) << 68); constexpr FF SUBLIMB_SHIFT(uint256_t(1) << 14); constexpr FF SUBLIMB_SHIFT_2(SUBLIMB_SHIFT * SUBLIMB_SHIFT); @@ -572,32 +562,28 @@ TEST_F(UltraRelationConsistency, AuxiliaryRelation) non_native_field_gate_2 *= LIMB_SIZE; non_native_field_gate_2 -= w_4_shift; non_native_field_gate_2 += limb_subproduct; - non_native_field_gate_2 *= q_4; limb_subproduct *= LIMB_SIZE; limb_subproduct += (w_1_shift * w_2_shift); auto non_native_field_gate_1 = limb_subproduct; non_native_field_gate_1 -= (w_3 + w_4); - non_native_field_gate_1 *= q_3; auto non_native_field_gate_3 = limb_subproduct; non_native_field_gate_3 += w_4; non_native_field_gate_3 -= (w_3_shift + w_4_shift); - non_native_field_gate_3 *= q_m; - auto non_native_field_identity = non_native_field_gate_1 + non_native_field_gate_2 + non_native_field_gate_3; - non_native_field_identity *= q_2; + auto non_native_field_identity = q_2 * q_3 * non_native_field_gate_1; + non_native_field_identity += q_2 * q_4 * non_native_field_gate_2; + non_native_field_identity += q_2 * q_m * non_native_field_gate_3; auto limb_accumulator_1 = w_1 + w_2 * SUBLIMB_SHIFT + w_3 * SUBLIMB_SHIFT_2 + w_1_shift * SUBLIMB_SHIFT_3 + w_2_shift * SUBLIMB_SHIFT_4 - w_4; - limb_accumulator_1 *= q_4; auto limb_accumulator_2 = w_3 + w_4 * SUBLIMB_SHIFT + w_1_shift * SUBLIMB_SHIFT_2 + w_2_shift * SUBLIMB_SHIFT_3 + w_3_shift * SUBLIMB_SHIFT_4 - w_4_shift; - limb_accumulator_2 *= q_m; - auto limb_accumulator_identity = limb_accumulator_1 + limb_accumulator_2; - limb_accumulator_identity *= q_3; + auto limb_accumulator_identity = q_3 * q_4 * limb_accumulator_1; + limb_accumulator_identity += q_3 * q_m * limb_accumulator_2; /** * MEMORY @@ -627,11 +613,9 @@ TEST_F(UltraRelationConsistency, AuxiliaryRelation) // auto adjacent_values_match_if_adjacent_indices_match = (FF(1) - index_delta) * record_delta; auto adjacent_values_match_if_adjacent_indices_match = (index_delta * FF(-1) + FF(1)) * record_delta; - auto ROM_consistency_check_identity = adjacent_values_match_if_adjacent_indices_match; - ROM_consistency_check_identity *= fake_alpha; - ROM_consistency_check_identity += index_is_monotonically_increasing; - ROM_consistency_check_identity *= fake_alpha; - ROM_consistency_check_identity += memory_record_check; + expected_full_length_univariates[1] = adjacent_values_match_if_adjacent_indices_match * (q_1 * q_2); + expected_full_length_univariates[2] = index_is_monotonically_increasing * (q_1 * q_2); + auto ROM_consistency_check_identity = memory_record_check * (q_1 * q_2); /** * RAM Consistency Check @@ -659,34 +643,42 @@ TEST_F(UltraRelationConsistency, AuxiliaryRelation) auto next_gate_access_type_is_boolean = next_gate_access_type * next_gate_access_type - next_gate_access_type; // Putting it all together... - auto RAM_consistency_check_identity = - adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation; - RAM_consistency_check_identity *= fake_alpha; - RAM_consistency_check_identity += index_is_monotonically_increasing; - RAM_consistency_check_identity *= fake_alpha; - RAM_consistency_check_identity += next_gate_access_type_is_boolean; - RAM_consistency_check_identity *= fake_alpha; - RAM_consistency_check_identity += access_check; + expected_full_length_univariates[3] = + adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation * (q_arith); + expected_full_length_univariates[4] = index_is_monotonically_increasing * (q_arith); + expected_full_length_univariates[5] = next_gate_access_type_is_boolean * (q_arith); + auto RAM_consistency_check_identity = access_check * (q_arith); + + /** + * RAM/ROM access check gate + */ + memory_record_check *= (q_1 * q_m); /** * RAM Timestamp Consistency Check */ auto timestamp_delta = w_2_shift - w_2; auto RAM_timestamp_check_identity = (index_delta * FF(-1) + FF(1)) * timestamp_delta - w_3; + RAM_timestamp_check_identity *= (q_1 * q_4); /** * The complete RAM/ROM memory identity */ - auto memory_identity = ROM_consistency_check_identity * q_2; - memory_identity += RAM_timestamp_check_identity * q_4; - memory_identity += memory_record_check * q_m; - memory_identity *= q_1; - memory_identity += (RAM_consistency_check_identity * q_arith); + auto memory_identity = ROM_consistency_check_identity; + memory_identity += RAM_timestamp_check_identity; + memory_identity += memory_record_check; + memory_identity += RAM_consistency_check_identity; + + expected_full_length_univariates[0] = memory_identity + non_native_field_identity + limb_accumulator_identity; - auto expected_evals = memory_identity + non_native_field_identity + limb_accumulator_identity; - expected_evals *= q_aux; + expected_full_length_univariates[0] *= q_aux; + expected_full_length_univariates[1] *= q_aux; + expected_full_length_univariates[2] *= q_aux; + expected_full_length_univariates[3] *= q_aux; + expected_full_length_univariates[4] *= q_aux; + expected_full_length_univariates[5] *= q_aux; - validate_evaluations(expected_evals, relation, extended_edges, relation_parameters); + validate_evaluations(expected_full_length_univariates, relation, extended_edges, relation_parameters); }; } // namespace proof_system::honk_relation_tests diff --git a/cpp/src/barretenberg/honk/sumcheck/sumcheck.hpp b/cpp/src/barretenberg/honk/sumcheck/sumcheck.hpp index 3e6794e18a..399b943870 100644 --- a/cpp/src/barretenberg/honk/sumcheck/sumcheck.hpp +++ b/cpp/src/barretenberg/honk/sumcheck/sumcheck.hpp @@ -1,7 +1,7 @@ #pragma once #include "barretenberg/common/serialize.hpp" #include -#include "barretenberg/honk/sumcheck/relations/relation.hpp" +#include "barretenberg/honk/sumcheck/relations/relation_parameters.hpp" #include "barretenberg/honk/transcript/transcript.hpp" #include "barretenberg/honk/utils/grand_product_delta.hpp" #include "barretenberg/common/throw_or_abort.hpp" diff --git a/cpp/src/barretenberg/honk/sumcheck/sumcheck.test.cpp b/cpp/src/barretenberg/honk/sumcheck/sumcheck.test.cpp index f319872b15..c7dee556ca 100644 --- a/cpp/src/barretenberg/honk/sumcheck/sumcheck.test.cpp +++ b/cpp/src/barretenberg/honk/sumcheck/sumcheck.test.cpp @@ -3,8 +3,7 @@ #include "barretenberg/honk/flavor/standard.hpp" #include "barretenberg/transcript/transcript_wrappers.hpp" #include "relations/arithmetic_relation.hpp" -#include "relations/grand_product_computation_relation.hpp" -#include "relations/grand_product_initialization_relation.hpp" +#include "relations/permutation_relation.hpp" #include "barretenberg/transcript/manifest.hpp" #include #include @@ -12,6 +11,13 @@ #include "barretenberg/ecc/curves/bn254/fr.hpp" #include #include "barretenberg/numeric/random/engine.hpp" +#include "barretenberg/honk/composer/standard_honk_composer.hpp" +#include "barretenberg/honk/composer/ultra_honk_composer.hpp" +#include "barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation.hpp" +#include "barretenberg/honk/sumcheck/relations/lookup_relation.hpp" +#include "barretenberg/honk/sumcheck/relations/gen_perm_sort_relation.hpp" +#include "barretenberg/honk/sumcheck/relations/elliptic_relation.hpp" +#include "barretenberg/honk/sumcheck/relations/auxiliary_relation.hpp" #include #include @@ -402,4 +408,304 @@ TEST(Sumcheck, ProverAndVerifierLonger) run_test(/* expect_verified=*/false); } +/** + * @brief Test the Standard Sumcheck Prover and Verifier for a real circuit + * + */ +TEST(Sumcheck, RealCircuitStandard) +{ + using Flavor = honk::flavor::Standard; + using FF = typename Flavor::FF; + using ProverPolynomials = typename Flavor::ProverPolynomials; + + // Create a composer and a dummy circuit with a few gates + auto composer = StandardHonkComposer(); + fr a = fr::one(); + // Using the public variable to check that public_input_delta is computed and added to the relation correctly + uint32_t a_idx = composer.add_public_variable(a); + fr b = fr::one(); + fr c = a + b; + fr d = a + c; + uint32_t b_idx = composer.add_variable(b); + uint32_t c_idx = composer.add_variable(c); + uint32_t d_idx = composer.add_variable(d); + for (size_t i = 0; i < 16; i++) { + composer.create_add_gate({ a_idx, b_idx, c_idx, fr::one(), fr::one(), fr::neg_one(), fr::zero() }); + composer.create_add_gate({ d_idx, c_idx, a_idx, fr::one(), fr::neg_one(), fr::neg_one(), fr::zero() }); + } + // Create a prover (it will compute proving key and witness) + auto prover = composer.create_prover(); + + // Generate beta and gamma + fr beta = fr::random_element(); + fr gamma = fr::random_element(); + + // Compute public input delta + const auto public_inputs = composer.circuit_constructor.get_public_inputs(); + auto public_input_delta = + honk::compute_public_input_delta(public_inputs, beta, gamma, prover.key->circuit_size); + + sumcheck::RelationParameters relation_parameters{ + .beta = beta, + .gamma = gamma, + .public_input_delta = public_input_delta, + }; + + // Compute grand product polynomial + polynomial z_permutation = prover_library::compute_permutation_grand_product(prover.key, beta, gamma); + + ProverPolynomials prover_polynomials; + + prover_polynomials.w_l = prover.key->w_l; + prover_polynomials.w_r = prover.key->w_r; + prover_polynomials.w_o = prover.key->w_o; + prover_polynomials.z_perm = z_permutation; + prover_polynomials.z_perm_shift = z_permutation.shifted(); + prover_polynomials.q_m = prover.key->q_m; + prover_polynomials.q_l = prover.key->q_l; + prover_polynomials.q_r = prover.key->q_r; + prover_polynomials.q_o = prover.key->q_o; + prover_polynomials.q_c = prover.key->q_c; + prover_polynomials.sigma_1 = prover.key->sigma_1; + prover_polynomials.sigma_2 = prover.key->sigma_2; + prover_polynomials.sigma_3 = prover.key->sigma_3; + prover_polynomials.id_1 = prover.key->id_1; + prover_polynomials.id_2 = prover.key->id_2; + prover_polynomials.id_3 = prover.key->id_3; + prover_polynomials.lagrange_first = prover.key->lagrange_first; + prover_polynomials.lagrange_last = prover.key->lagrange_last; + + auto prover_transcript = ProverTranscript::init_empty(); + + auto sumcheck_prover = Sumcheck>(prover.key->circuit_size, prover_transcript); + + auto prover_output = sumcheck_prover.execute_prover(prover_polynomials, relation_parameters); + + auto verifier_transcript = VerifierTranscript::init_empty(prover_transcript); + + auto sumcheck_verifier = Sumcheck>(prover.key->circuit_size, verifier_transcript); + + std::optional verifier_output = sumcheck_verifier.execute_verifier(relation_parameters); + + ASSERT_TRUE(verifier_output.has_value()); +} + +/** + * @brief Test the Ultra Sumcheck Prover and Verifier for a real circuit + * + */ +TEST(Sumcheck, RealCircuitUltra) +{ + using Flavor = honk::flavor::Ultra; + using FF = typename Flavor::FF; + using ProverPolynomials = typename Flavor::ProverPolynomials; + + // Create a composer and a dummy circuit with a few gates + auto composer = UltraHonkComposer(); + fr a = fr::one(); + + // Add some basic add gates + uint32_t a_idx = composer.add_variable(a); + fr b = fr::one(); + fr c = a + b; + fr d = a + c; + uint32_t b_idx = composer.add_variable(b); + uint32_t c_idx = composer.add_variable(c); + uint32_t d_idx = composer.add_variable(d); + for (size_t i = 0; i < 16; i++) { + composer.create_add_gate({ a_idx, b_idx, c_idx, 1, 1, -1, 0 }); + composer.create_add_gate({ d_idx, c_idx, a_idx, 1, -1, -1, 0 }); + } + + // Add a big add gate with use of next row to test q_arith = 2 + fr e = a + b + c + d; + uint32_t e_idx = composer.add_variable(e); + + uint32_t zero_idx = composer.get_zero_idx(); + composer.create_big_add_gate({ a_idx, b_idx, c_idx, d_idx, -1, -1, -1, -1, 0 }, true); // use next row + composer.create_big_add_gate({ zero_idx, zero_idx, zero_idx, e_idx, 0, 0, 0, 0, 0 }, false); + + // Add some lookup gates (related to pedersen hashing) + barretenberg::fr pedersen_input_value = fr::random_element(); + const fr input_hi = uint256_t(pedersen_input_value).slice(126, 256); + const fr input_lo = uint256_t(pedersen_input_value).slice(0, 126); + const auto input_hi_index = composer.add_variable(input_hi); + const auto input_lo_index = composer.add_variable(input_lo); + + const auto sequence_data_hi = plookup::get_lookup_accumulators(plookup::MultiTableId::PEDERSEN_LEFT_HI, input_hi); + const auto sequence_data_lo = plookup::get_lookup_accumulators(plookup::MultiTableId::PEDERSEN_LEFT_LO, input_lo); + + composer.create_gates_from_plookup_accumulators( + plookup::MultiTableId::PEDERSEN_LEFT_HI, sequence_data_hi, input_hi_index); + composer.create_gates_from_plookup_accumulators( + plookup::MultiTableId::PEDERSEN_LEFT_LO, sequence_data_lo, input_lo_index); + + // Add a sort gate (simply checks that consecutive inputs have a difference of < 4) + a_idx = composer.add_variable(FF(0)); + b_idx = composer.add_variable(FF(1)); + c_idx = composer.add_variable(FF(2)); + d_idx = composer.add_variable(FF(3)); + composer.create_sort_constraint({ a_idx, b_idx, c_idx, d_idx }); + + // Add an elliptic curve addition gate + grumpkin::g1::affine_element p1 = crypto::generators::get_generator_data({ 0, 0 }).generator; + grumpkin::g1::affine_element p2 = crypto::generators::get_generator_data({ 0, 1 }).generator; + + grumpkin::fq beta_scalar = grumpkin::fq::cube_root_of_unity(); + grumpkin::g1::affine_element p2_endo = p2; + p2_endo.x *= beta_scalar; + + grumpkin::g1::affine_element p3(grumpkin::g1::element(p1) - grumpkin::g1::element(p2_endo)); + + uint32_t x1 = composer.add_variable(p1.x); + uint32_t y1 = composer.add_variable(p1.y); + uint32_t x2 = composer.add_variable(p2.x); + uint32_t y2 = composer.add_variable(p2.y); + uint32_t x3 = composer.add_variable(p3.x); + uint32_t y3 = composer.add_variable(p3.y); + + ecc_add_gate gate{ x1, y1, x2, y2, x3, y3, beta_scalar, -1 }; + composer.create_ecc_add_gate(gate); + + // Add some RAM gates + uint32_t ram_values[8]{ + composer.add_variable(fr::random_element()), composer.add_variable(fr::random_element()), + composer.add_variable(fr::random_element()), composer.add_variable(fr::random_element()), + composer.add_variable(fr::random_element()), composer.add_variable(fr::random_element()), + composer.add_variable(fr::random_element()), composer.add_variable(fr::random_element()), + }; + + size_t ram_id = composer.create_RAM_array(8); + + for (size_t i = 0; i < 8; ++i) { + composer.init_RAM_element(ram_id, i, ram_values[i]); + } + + a_idx = composer.read_RAM_array(ram_id, composer.add_variable(5)); + EXPECT_EQ(a_idx != ram_values[5], true); + + b_idx = composer.read_RAM_array(ram_id, composer.add_variable(4)); + c_idx = composer.read_RAM_array(ram_id, composer.add_variable(1)); + + composer.write_RAM_array(ram_id, composer.add_variable(4), composer.add_variable(500)); + d_idx = composer.read_RAM_array(ram_id, composer.add_variable(4)); + + EXPECT_EQ(composer.get_variable(d_idx), 500); + + // ensure these vars get used in another arithmetic gate + const auto e_value = composer.get_variable(a_idx) + composer.get_variable(b_idx) + composer.get_variable(c_idx) + + composer.get_variable(d_idx); + e_idx = composer.add_variable(e_value); + + composer.create_big_add_gate({ a_idx, b_idx, c_idx, d_idx, -1, -1, -1, -1, 0 }, true); + composer.create_big_add_gate( + { + composer.get_zero_idx(), + composer.get_zero_idx(), + composer.get_zero_idx(), + e_idx, + 0, + 0, + 0, + 0, + 0, + }, + false); + + // Create a prover (it will compute proving key and witness) + auto prover = composer.create_prover(); + + // Generate eta, beta and gamma + fr eta = fr::random_element(); + fr beta = fr::random_element(); + fr gamma = fr::random_element(); + + // Compute public input delta + const auto public_inputs = composer.circuit_constructor.get_public_inputs(); + auto public_input_delta = + honk::compute_public_input_delta(public_inputs, beta, gamma, prover.key->circuit_size); + auto lookup_grand_product_delta = + honk::compute_lookup_grand_product_delta(beta, gamma, prover.key->circuit_size); + + sumcheck::RelationParameters relation_parameters{ + .eta = eta, + .beta = beta, + .gamma = gamma, + .public_input_delta = public_input_delta, + .lookup_grand_product_delta = lookup_grand_product_delta, + }; + + // Compute sorted witness-table accumulator + prover.key->sorted_accum = prover_library::compute_sorted_list_accumulator(prover.key, eta); + + // Add RAM/ROM memory records to wire four + prover_library::add_plookup_memory_records_to_wire_4(prover.key, eta); + + // Compute grand product polynomial + prover.key->z_perm = prover_library::compute_permutation_grand_product(prover.key, beta, gamma); + + // Compute lookup grand product polynomial + prover.key->z_lookup = prover_library::compute_lookup_grand_product(prover.key, eta, beta, gamma); + + ProverPolynomials prover_polynomials; + + prover_polynomials.w_l = prover.key->w_l; + prover_polynomials.w_r = prover.key->w_r; + prover_polynomials.w_o = prover.key->w_o; + prover_polynomials.w_4 = prover.key->w_4; + prover_polynomials.w_l_shift = prover.key->w_l.shifted(); + prover_polynomials.w_r_shift = prover.key->w_r.shifted(); + prover_polynomials.w_o_shift = prover.key->w_o.shifted(); + prover_polynomials.w_4_shift = prover.key->w_4.shifted(); + prover_polynomials.sorted_accum = prover.key->sorted_accum; + prover_polynomials.sorted_accum_shift = prover.key->sorted_accum.shifted(); + prover_polynomials.table_1 = prover.key->table_1; + prover_polynomials.table_2 = prover.key->table_2; + prover_polynomials.table_3 = prover.key->table_3; + prover_polynomials.table_4 = prover.key->table_4; + prover_polynomials.table_1_shift = prover.key->table_1.shifted(); + prover_polynomials.table_2_shift = prover.key->table_2.shifted(); + prover_polynomials.table_3_shift = prover.key->table_3.shifted(); + prover_polynomials.table_4_shift = prover.key->table_4.shifted(); + prover_polynomials.z_perm = prover.key->z_perm; + prover_polynomials.z_perm_shift = prover.key->z_perm.shifted(); + prover_polynomials.z_lookup = prover.key->z_lookup; + prover_polynomials.z_lookup_shift = prover.key->z_lookup.shifted(); + prover_polynomials.q_m = prover.key->q_m; + prover_polynomials.q_l = prover.key->q_l; + prover_polynomials.q_r = prover.key->q_r; + prover_polynomials.q_o = prover.key->q_o; + prover_polynomials.q_c = prover.key->q_c; + prover_polynomials.q_4 = prover.key->q_4; + prover_polynomials.q_arith = prover.key->q_arith; + prover_polynomials.q_sort = prover.key->q_sort; + prover_polynomials.q_elliptic = prover.key->q_elliptic; + prover_polynomials.q_aux = prover.key->q_aux; + prover_polynomials.q_lookup = prover.key->q_lookup; + prover_polynomials.sigma_1 = prover.key->sigma_1; + prover_polynomials.sigma_2 = prover.key->sigma_2; + prover_polynomials.sigma_3 = prover.key->sigma_3; + prover_polynomials.sigma_4 = prover.key->sigma_4; + prover_polynomials.id_1 = prover.key->id_1; + prover_polynomials.id_2 = prover.key->id_2; + prover_polynomials.id_3 = prover.key->id_3; + prover_polynomials.id_4 = prover.key->id_4; + prover_polynomials.lagrange_first = prover.key->lagrange_first; + prover_polynomials.lagrange_last = prover.key->lagrange_last; + + auto prover_transcript = ProverTranscript::init_empty(); + + auto sumcheck_prover = Sumcheck>(prover.key->circuit_size, prover_transcript); + + auto prover_output = sumcheck_prover.execute_prover(prover_polynomials, relation_parameters); + + auto verifier_transcript = VerifierTranscript::init_empty(prover_transcript); + + auto sumcheck_verifier = Sumcheck>(prover.key->circuit_size, verifier_transcript); + + std::optional verifier_output = sumcheck_verifier.execute_verifier(relation_parameters); + + ASSERT_TRUE(verifier_output.has_value()); +} + } // namespace test_sumcheck_round diff --git a/cpp/src/barretenberg/honk/sumcheck/sumcheck_round.hpp b/cpp/src/barretenberg/honk/sumcheck/sumcheck_round.hpp index 4c2fbca313..a90d422e51 100644 --- a/cpp/src/barretenberg/honk/sumcheck/sumcheck_round.hpp +++ b/cpp/src/barretenberg/honk/sumcheck/sumcheck_round.hpp @@ -6,8 +6,9 @@ #include "polynomials/barycentric_data.hpp" #include "polynomials/univariate.hpp" #include "polynomials/pow.hpp" -#include "relations/relation.hpp" -#include "barretenberg/honk/flavor/ultra.hpp" // WORKTOO +#include "relations/relation_parameters.hpp" +#include "barretenberg/honk/flavor/ultra.hpp" +#include namespace proof_system::honk::sumcheck { @@ -56,8 +57,8 @@ namespace proof_system::honk::sumcheck { template class SumcheckRound { using Relations = typename Flavor::Relations; - using UnivariateTuple = typename Flavor::UnivariateTuple; - using BarycentricUtils = typename Flavor::BarycentricUtils; + using RelationUnivariates = typename Flavor::RelationUnivariates; + using RelationEvaluations = typename Flavor::RelationValues; public: using FF = typename Flavor::FF; @@ -74,79 +75,46 @@ template class SumcheckRound { FF target_total_sum = 0; - // TODO(#224)(Cody): this barycentric stuff should be more built-in? - BarycentricUtils barycentric_utils; - UnivariateTuple univariate_accumulators; - std::array relation_evaluations; + RelationUnivariates univariate_accumulators; + RelationEvaluations relation_evaluations; + ExtendedEdges extended_edges; - std::array, NUM_RELATIONS> extended_univariates; - // TODO(#224)(Cody): this should go away and we should use constexpr method to extend + // TODO(#224)(Cody): this should go away BarycentricData barycentric_2_to_max = BarycentricData(); // Prover constructor SumcheckRound(size_t initial_round_size) : round_size(initial_round_size) - {} - - // Verifier constructor - explicit SumcheckRound() - { - // FF's default constructor may not initialize to zero (e.g., barretenberg::fr), hence we can't rely on - // aggregate initialization of the evaluations array. - std::fill(relation_evaluations.begin(), relation_evaluations.end(), FF(0)); - }; - - /** - * @brief After computing the round univariate, it is necessary to zero-out the accumulators used to compute it. - */ - template void reset_accumulators() { - auto& univariate = std::get(univariate_accumulators); - std::fill(univariate.evaluations.begin(), univariate.evaluations.end(), FF(0)); - - if constexpr (idx + 1 < NUM_RELATIONS) { - reset_accumulators(); - } - }; - // IMPROVEMENT(Cody): This is kind of ugly. There should be a one-liner with folding - // or std::apply or something. + // Initialize univariate accumulators to 0 + zero_univariates(univariate_accumulators); + } - /** - * @brief Given a tuple t = (t_0, t_1, ..., t_{NUM_RELATIONS-1}) and a challenge α, - * modify the tuple in place to (t_0, αt_1, ..., α^{NUM_RELATIONS-1}t_{NUM_RELATIONS-1}). - */ - template void scale_tuple(auto& tuple, FF challenge, FF running_challenge) - { - std::get(tuple) *= running_challenge; - running_challenge *= challenge; - if constexpr (idx + 1 < NUM_RELATIONS) { - scale_tuple(tuple, challenge, running_challenge); - } - }; + // Verifier constructor + explicit SumcheckRound() { zero_elements(relation_evaluations); }; /** * @brief Given a tuple t = (t_0, t_1, ..., t_{NUM_RELATIONS-1}) and a challenge α, * return t_0 + αt_1 + ... + α^{NUM_RELATIONS-1}t_{NUM_RELATIONS-1}). * - * @tparam T : In practice, this is an FF or a Univariate. + * @tparam T : In practice, this is a Univariate. */ - template T batch_over_relations(FF challenge) + Univariate batch_over_relations(FF challenge) { FF running_challenge = 1; - scale_tuple<>(univariate_accumulators, challenge, running_challenge); - extend_univariate_accumulators<>(); - auto result = T(); - for (size_t i = 0; i < NUM_RELATIONS; ++i) { - result += extended_univariates[i]; - } + scale_univariates(univariate_accumulators, challenge, running_challenge); + + auto result = Univariate(); + extend_and_batch_univariates(univariate_accumulators, result); + + // Reset all univariate accumulators to 0 before beginning accumulation in the next round + zero_univariates(univariate_accumulators); return result; } /** - * @brief Evaluate some relations by evaluating each edge in the edge group at - * Univariate::length-many values. Store each value separately in the corresponding - * entry of relation_evals. + * @brief Extend each edge in the edge group at to max-relation-length-many values. * * @details Should only be called externally with relation_idx equal to 0. * In practice, multivariates is one of ProverPolynomials or FoldedPolynomials. @@ -162,23 +130,6 @@ template class SumcheckRound { } } - /** - * @brief After executing each widget on each edge, producing a tuple of univariates of differing lenghths, - * extend all univariates to the max of the lenghths required by the largest relation. - * - * @tparam relation_idx - */ - template void extend_univariate_accumulators() - { - extended_univariates[relation_idx] = - std::get(barycentric_utils).extend(std::get(univariate_accumulators)); - - // Repeat for the next relation. - if constexpr (relation_idx + 1 < NUM_RELATIONS) { - extend_univariate_accumulators(); - } - } - /** * @brief Return the evaluations of the univariate restriction (S_l(X_l) in the thesis) at num_multivariates-many * values. Most likely this will end up being S_l(0), ... , S_l(t-1) where t is around 12. At the end, reset all @@ -203,11 +154,7 @@ template class SumcheckRound { pow_challenge *= pow_univariate.zeta_pow_sqr; } - auto result = batch_over_relations>(alpha); - - reset_accumulators<>(); - - return result; + return batch_over_relations(alpha); } /** @@ -225,13 +172,10 @@ template class SumcheckRound { { accumulate_relation_evaluations<>(purported_evaluations, relation_parameters); - // IMPROVEMENT(Cody): Reuse functions from univariate_accumulators batching? - FF running_challenge = 1; - FF output = 0; - for (auto& evals : relation_evaluations) { - output += evals * running_challenge; - running_challenge *= alpha; - } + auto running_challenge = FF(1); + auto output = FF(0); + scale_and_batch_elements(relation_evaluations, alpha, running_challenge, output); + output *= pow_univariate.partial_evaluation_constant; return output; @@ -320,12 +264,142 @@ template class SumcheckRound { const RelationParameters& relation_parameters) { std::get(relations).add_full_relation_value_contribution( - relation_evaluations[relation_idx], purported_evaluations, relation_parameters); + std::get(relation_evaluations), purported_evaluations, relation_parameters); // Repeat for the next relation. if constexpr (relation_idx + 1 < NUM_RELATIONS) { accumulate_relation_evaluations(purported_evaluations, relation_parameters); } } + + public: + /** + * Utility methods for tuple of tuples of Univariates + */ + + /** + * @brief Extend Univariates to specified size then sum them + * + * @tparam extended_size Size after extension + * @param tuple A tuple of tuples of Univariates + * @param result A Univariate of length extended_size + */ + template + static void extend_and_batch_univariates(auto& tuple, Univariate& result) + { + auto extend_and_sum = [&](auto& element) { + using Element = std::remove_reference_t; + // TODO(#224)(Cody): this barycentric stuff should be more built-in? + BarycentricData barycentric_utils; + result += barycentric_utils.extend(element); + }; + apply_to_tuple_of_tuples(tuple, extend_and_sum); + } + + /** + * @brief Set all coefficients of Univariates to zero + * + * @details After computing the round univariate, it is necessary to zero-out the accumulators used to compute it. + */ + static void zero_univariates(auto& tuple) + { + auto set_to_zero = [](auto& element) { + std::fill(element.evaluations.begin(), element.evaluations.end(), FF(0)); + }; + apply_to_tuple_of_tuples(tuple, set_to_zero); + } + + /** + * @brief Scale Univaraites by consecutive powers of the provided challenge + * + * @param tuple Tuple of tuples of Univariates + * @param challenge + * @param current_scalar power of the challenge + */ + static void scale_univariates(auto& tuple, const FF& challenge, FF current_scalar) + { + auto scale_by_consecutive_powers_of_challenge = [&](auto& element) { + element *= current_scalar; + current_scalar *= challenge; + }; + apply_to_tuple_of_tuples(tuple, scale_by_consecutive_powers_of_challenge); + } + + /** + * @brief General purpose method for applying an operation to a tuple of tuples of Univariates + * + * @tparam Operation Any operation valid on Univariates + * @tparam outer_idx Index into the outer tuple + * @tparam inner_idx Index into the inner tuple + * @param tuple A Tuple of tuples of Univariates + * @param operation Operation to apply to Univariates + */ + template + static void apply_to_tuple_of_tuples(auto& tuple, Operation&& operation) + { + auto& inner_tuple = std::get(tuple); + auto& univariate = std::get(inner_tuple); + + // Apply the specified operation to each Univariate + std::invoke(std::forward(operation), univariate); + + const size_t inner_size = std::tuple_size_v(tuple))>>; + const size_t outer_size = std::tuple_size_v>; + + // Recurse over inner and outer tuples + if constexpr (inner_idx + 1 < inner_size) { + apply_to_tuple_of_tuples(tuple, std::forward(operation)); + } else if constexpr (outer_idx + 1 < outer_size) { + apply_to_tuple_of_tuples(tuple, std::forward(operation)); + } + } + + /** + * Utility methods for tuple of arrays + */ + + /** + * @brief Set each element in a tuple of arrays to zero. + * @details FF's default constructor may not initialize to zero (e.g., barretenberg::fr), hence we can't rely on + * aggregate initialization of the evaluations array. + */ + template static void zero_elements(auto& tuple) + { + auto set_to_zero = [](auto& element) { std::fill(element.begin(), element.end(), FF(0)); }; + apply_to_tuple_of_arrays(set_to_zero, tuple); + }; + + /** + * @brief Scale elements by consecutive powers of the challenge then sum + * @param result Batched result + */ + static void scale_and_batch_elements(auto& tuple, const FF& challenge, FF current_scalar, FF& result) + { + auto scale_by_challenge_and_accumulate = [&](auto& element) { + for (auto& entry : element) { + result += entry * current_scalar; + current_scalar *= challenge; + } + }; + apply_to_tuple_of_arrays(scale_by_challenge_and_accumulate, tuple); + } + + /** + * @brief General purpose method for applying a tuple of arrays (of FFs) + * + * @tparam Operation Any operation valid on elements of the inner arrays (FFs) + * @param tuple Tuple of arrays (of FFs) + */ + template + static void apply_to_tuple_of_arrays(Operation&& operation, std::tuple& tuple) + { + auto& element = std::get(tuple); + + std::invoke(std::forward(operation), element); + + if constexpr (idx + 1 < sizeof...(Ts)) { + apply_to_tuple_of_arrays(operation, tuple); + } + } }; } // namespace proof_system::honk::sumcheck diff --git a/cpp/src/barretenberg/honk/sumcheck/sumcheck_round.test.cpp b/cpp/src/barretenberg/honk/sumcheck/sumcheck_round.test.cpp index 9efdc23677..c5e7260be4 100644 --- a/cpp/src/barretenberg/honk/sumcheck/sumcheck_round.test.cpp +++ b/cpp/src/barretenberg/honk/sumcheck/sumcheck_round.test.cpp @@ -29,46 +29,6 @@ const size_t max_relation_length = 5; const size_t input_polynomial_length = 2; namespace test_sumcheck_round { -template -void construct_full_polynomials(ProverPolynomials& full_polynomials, - std::array& w_l, - std::array& w_r, - std::array& w_o, - std::array& z_perm, - std::array& z_perm_shift, - std::array& q_m, - std::array& q_l, - std::array& q_r, - std::array& q_o, - std::array& q_c, - std::array& sigma_1, - std::array& sigma_2, - std::array& sigma_3, - std::array& id_1, - std::array& id_2, - std::array& id_3, - std::array& lagrange_first, - std::array& lagrange_last) -{ - full_polynomials.w_l = w_l; - full_polynomials.w_r = w_r; - full_polynomials.w_o = w_o; - full_polynomials.z_perm = z_perm; - full_polynomials.z_perm_shift = z_perm_shift; - full_polynomials.q_m = q_m; - full_polynomials.q_l = q_l; - full_polynomials.q_r = q_r; - full_polynomials.q_o = q_o; - full_polynomials.q_c = q_c; - full_polynomials.sigma_1 = sigma_1; - full_polynomials.sigma_2 = sigma_2; - full_polynomials.sigma_3 = sigma_3; - full_polynomials.id_1 = id_1; - full_polynomials.id_2 = id_2; - full_polynomials.id_3 = id_3; - full_polynomials.lagrange_first = lagrange_first; - full_polynomials.lagrange_last = lagrange_last; -} // The below two methods are used in the test ComputeUnivariateProver static Univariate compute_round_univariate( @@ -77,48 +37,29 @@ static Univariate compute_round_univariate( const FF alpha) { size_t round_size = 1; - // Improvement(Cody): This is ugly? Maye supply some/all of this data through "flavor" class? auto round = SumcheckRound(round_size); - auto w_l = input_polynomials[0]; - auto w_r = input_polynomials[1]; - auto w_o = input_polynomials[2]; - auto z_perm = input_polynomials[3]; - auto z_perm_shift = input_polynomials[4]; - auto q_m = input_polynomials[5]; - auto q_l = input_polynomials[6]; - auto q_r = input_polynomials[7]; - auto q_o = input_polynomials[8]; - auto q_c = input_polynomials[9]; - auto sigma_1 = input_polynomials[10]; - auto sigma_2 = input_polynomials[11]; - auto sigma_3 = input_polynomials[12]; - auto id_1 = input_polynomials[13]; - auto id_2 = input_polynomials[14]; - auto id_3 = input_polynomials[15]; - auto lagrange_first = input_polynomials[16]; - auto lagrange_last = input_polynomials[17]; ProverPolynomials full_polynomials; - construct_full_polynomials(full_polynomials, - w_l, - w_r, - w_o, - z_perm, - z_perm_shift, - q_m, - q_l, - q_r, - q_o, - q_c, - sigma_1, - sigma_2, - sigma_3, - id_1, - id_2, - id_3, - lagrange_first, - lagrange_last); + full_polynomials.w_l = input_polynomials[0]; + full_polynomials.w_r = input_polynomials[1]; + full_polynomials.w_o = input_polynomials[2]; + full_polynomials.z_perm = input_polynomials[3]; + full_polynomials.z_perm_shift = input_polynomials[4]; + full_polynomials.q_m = input_polynomials[5]; + full_polynomials.q_l = input_polynomials[6]; + full_polynomials.q_r = input_polynomials[7]; + full_polynomials.q_o = input_polynomials[8]; + full_polynomials.q_c = input_polynomials[9]; + full_polynomials.sigma_1 = input_polynomials[10]; + full_polynomials.sigma_2 = input_polynomials[11]; + full_polynomials.sigma_3 = input_polynomials[12]; + full_polynomials.id_1 = input_polynomials[13]; + full_polynomials.id_2 = input_polynomials[14]; + full_polynomials.id_3 = input_polynomials[15]; + full_polynomials.lagrange_first = input_polynomials[16]; + full_polynomials.lagrange_last = input_polynomials[17]; + PowUnivariate pow_zeta(1); Univariate round_univariate = round.compute_univariate(full_polynomials, relation_parameters, pow_zeta, alpha); @@ -331,4 +272,93 @@ TEST(SumcheckRound, ComputeUnivariateVerifier) run_test(/* is_random_input=*/true); } +/** + * @brief Test utility functions for applying operations to tuple of tuple of Univariates + * + */ +TEST(SumcheckRound, TupleOfTuplesOfUnivariates) +{ + using Flavor = proof_system::honk::flavor::Standard; + using FF = typename Flavor::FF; + + // Define three linear univariates of different sizes + Univariate univariate_1({ 1, 2, 3 }); + Univariate univariate_2({ 2, 4 }); + Univariate univariate_3({ 3, 4, 5, 6, 7 }); + const size_t MAX_LENGTH = 5; + + // Instantiate some barycentric extension utility classes + auto barycentric_util_1 = BarycentricData(); + auto barycentric_util_2 = BarycentricData(); + auto barycentric_util_3 = BarycentricData(); + + // Construct a tuple of tuples of the form { {univariate_1}, {univariate_2, univariate_3} } + auto tuple_of_tuples = std::make_tuple(std::make_tuple(univariate_1), std::make_tuple(univariate_2, univariate_3)); + + // Use scale_univariate_accumulators to scale by challenge powers + FF challenge = 5; + FF running_challenge = 1; + SumcheckRound::scale_univariates(tuple_of_tuples, challenge, running_challenge); + + // Use extend_and_batch_univariates to extend to MAX_LENGTH then accumulate + auto result = Univariate(); + SumcheckRound::extend_and_batch_univariates(tuple_of_tuples, result); + + // Repeat the batching process manually + auto result_expected = barycentric_util_1.extend(univariate_1) * 1 + + barycentric_util_2.extend(univariate_2) * challenge + + barycentric_util_3.extend(univariate_3) * challenge * challenge; + + // Compare final batched univarites + EXPECT_EQ(result, result_expected); + + // Reinitialize univariate accumulators to zero + SumcheckRound::zero_univariates(tuple_of_tuples); + + // Check that reinitialization was successful + Univariate expected_1({ 0, 0, 0 }); + Univariate expected_2({ 0, 0 }); + Univariate expected_3({ 0, 0, 0, 0, 0 }); + EXPECT_EQ(std::get<0>(std::get<0>(tuple_of_tuples)), expected_1); + EXPECT_EQ(std::get<0>(std::get<1>(tuple_of_tuples)), expected_2); + EXPECT_EQ(std::get<1>(std::get<1>(tuple_of_tuples)), expected_3); +} + +/** + * @brief Test utility functions for applying operations to tuple of std::arrays of field elements + * + */ +TEST(SumcheckRound, TuplesOfEvaluationArrays) +{ + using Flavor = proof_system::honk::flavor::Standard; + using FF = typename Flavor::FF; + + // Define two arrays of arbitrary elements + std::array evaluations_1 = { 4 }; + std::array evaluations_2 = { 6, 2 }; + + // Construct a tuple + auto tuple_of_arrays = std::make_tuple(evaluations_1, evaluations_2); + + // Use scale_and_batch_elements to scale by challenge powers + FF challenge = 5; + FF running_challenge = 1; + FF result = 0; + SumcheckRound::scale_and_batch_elements(tuple_of_arrays, challenge, running_challenge, result); + + // Repeat the batching process manually + auto result_expected = + evaluations_1[0] * 1 + evaluations_2[0] * challenge + evaluations_2[1] * challenge * challenge; + + // Compare batched result + EXPECT_EQ(result, result_expected); + + // Reinitialize univariate accumulators to zero + SumcheckRound::zero_elements(tuple_of_arrays); + + EXPECT_EQ(std::get<0>(tuple_of_arrays)[0], 0); + EXPECT_EQ(std::get<1>(tuple_of_arrays)[0], 0); + EXPECT_EQ(std::get<1>(tuple_of_arrays)[1], 0); +} + } // namespace test_sumcheck_round diff --git a/cpp/src/barretenberg/proof_system/flavor/flavor.hpp b/cpp/src/barretenberg/proof_system/flavor/flavor.hpp index d42e3c6c9a..f150a277a2 100644 --- a/cpp/src/barretenberg/proof_system/flavor/flavor.hpp +++ b/cpp/src/barretenberg/proof_system/flavor/flavor.hpp @@ -231,16 +231,53 @@ template static constexpr size_t get_max } /** - * @brief Recursive utility function to construct tuple of Univariates of length RELATION_LENGTH - * + * @brief Recursive utility function to construct tuple of tuple of Univariates + * @details This is the container for storing the univariate contributions from each identity in each relation. Each + * Relation contributes a tuple with num-identities many Univariates and there are num-relations many tuples in the + * outer tuple. */ -template static constexpr auto create_univariate_tuple() +template static constexpr auto create_relation_univariates_container() { if constexpr (Index >= std::tuple_size::value) { return std::tuple<>{}; // Return empty when reach end of the tuple } else { - using UnivariateType = sumcheck::Univariate::RELATION_LENGTH>; - return std::tuple_cat(std::tuple{}, create_univariate_tuple()); + using UnivariateTuple = typename std::tuple_element_t::RelationUnivariates; + return std::tuple_cat(std::tuple{}, + create_relation_univariates_container()); + } +} + +/** + * @brief Recursive utility function to construct tuple of arrays + * @details Container for storing value of each identity in each relation. Each Relation contributes an array of + * length num-identities. + */ +template static constexpr auto create_relation_values_container() +{ + if constexpr (Index >= std::tuple_size::value) { + return std::tuple<>{}; // Return empty when reach end of the tuple + } else { + using ValuesArray = typename std::tuple_element_t::RelationValues; + return std::tuple_cat(std::tuple{}, create_relation_values_container()); + } +} + +/** + * @brief Recursive helper function to instantiate BarycentricData to extend each Relation in a tuple + * @details Instantiate with lengths 2, 3, ... ExtendedLength + * @note The purpose of this function is simply to instantiate some BarycentricData so that the static member + * arrays are computed at compile time. It thus does not need a return value. It's awkward however to make + * void functions execute at compile time so we make it return true upon completion and wrap the call in a + * static_assert to ensure it has executed correctly. + */ +template static constexpr bool instantiate_barycentric_utils() +{ + if constexpr (Length > ExtendedLength) { // include ExtendedLength + return true; // Return true when finished + } else { + // We dont need to keep the result, we just want to ensure compile time computation of static members + [[maybe_unused]] auto barycentric_data = sumcheck::BarycentricData{}; + return instantiate_barycentric_utils(); } }