From f4e1c44cfa056d7dca12fbafc5b52b2141791e2f Mon Sep 17 00:00:00 2001 From: Onyeka Obi Date: Fri, 10 Apr 2026 17:31:21 -0700 Subject: [PATCH] perf(eccvm): split set relation into 3 grand products to cut sumcheck degree (#2226) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Splits the single ECCVMSetRelation (partial length 22) into three independent grand products, one per tuple family: - ECCVMSetWnafRelation (pc, round, wnaf_slice) — partial length 9 - ECCVMSetScalarRelation (pc, P.x, P.y, scalar) — partial length 8 - ECCVMSetMsmRelation (pc, P.x, P.y, msm_size) — partial length 6 The wnaf grand product caches the product of its first two denominator factors in a new committed polynomial `den_wnaf_partial`, reducing degree from 10 to 9. Net effect: MAX_PARTIAL_RELATION_LENGTH drops from 22 to 9, and BATCHED_RELATION_PARTIAL_LENGTH from 24 to 11, which directly reduces sumcheck prover work for ECCVM proving. New entities: z_perm_scalar, z_perm_msm (shifted grand products), den_wnaf_partial (intermediate, depends on β/γ so committed in the log-derivative round). Entity counts updated accordingly (NUM_WITNESS_ENTITIES 87→90, NUM_SHIFTED_ENTITIES 26→28, NUM_ALL_ENTITIES 118→123). --- .../src/barretenberg/eccvm/eccvm_flavor.hpp | 53 +- .../src/barretenberg/eccvm/eccvm_prover.cpp | 48 ++ .../eccvm/eccvm_relation_corruption.test.cpp | 68 +- .../eccvm/eccvm_trace_checker.cpp | 35 +- .../src/barretenberg/eccvm/eccvm_verifier.cpp | 6 + .../relations/ecc_vm/ecc_set_relation.cpp | 12 +- .../relations/ecc_vm/ecc_set_relation.hpp | 184 ++++- .../ecc_vm/ecc_set_relation_impl.hpp | 750 +++++++++--------- .../ecc_relation_consistency.test.cpp | 4 +- .../eccvm_verifier/ecc_set_relation.cpp | 12 +- 10 files changed, 709 insertions(+), 463 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_flavor.hpp b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_flavor.hpp index 0a59e35ba7df..df9e242127c7 100644 --- a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_flavor.hpp +++ b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_flavor.hpp @@ -24,7 +24,7 @@ #include "barretenberg/relations/ecc_vm/ecc_lookup_relation.hpp" #include "barretenberg/relations/ecc_vm/ecc_msm_relation.hpp" #include "barretenberg/relations/ecc_vm/ecc_point_table_relation.hpp" -#include "barretenberg/relations/ecc_vm/ecc_set_relation.hpp" +#include "barretenberg/relations/ecc_vm/ecc_set_relation.hpp" // ECCVMSetWnaf/Scalar/MsmRelation #include "barretenberg/relations/ecc_vm/ecc_transcript_relation.hpp" #include "barretenberg/relations/ecc_vm/ecc_wnaf_relation.hpp" #include "barretenberg/relations/relation_parameters.hpp" @@ -74,18 +74,16 @@ class ECCVMFlavor { // The number of multivariate polynomials on which a sumcheck prover sumcheck operates (including shifts). We often // need containers of this size to hold related data, so we choose a name more agnostic than `NUM_POLYNOMIALS`. // Note: this number does not include the individual sorted list polynomials. - // Includes gemini_masking_poly for ZK (NUM_ALL_ENTITIES = 117 + NUM_MASKING_POLYNOMIALS) - static constexpr size_t NUM_ALL_ENTITIES = 118; + static constexpr size_t NUM_ALL_ENTITIES = 123; // The number of polynomials precomputed to describe a circuit and to aid a prover in constructing a satisfying // assignment of witnesses. We again choose a neutral name. static constexpr size_t NUM_PRECOMPUTED_ENTITIES = 4; // The total number of witness entities not including shifts. - // Includes gemini_masking_poly for ZK (NUM_WITNESS_ENTITIES = 86 + NUM_MASKING_POLYNOMIALS) - static constexpr size_t NUM_WITNESS_ENTITIES = 87; + static constexpr size_t NUM_WITNESS_ENTITIES = 90; // The number of entities in ShiftedEntities. - static constexpr size_t NUM_SHIFTED_ENTITIES = 26; + static constexpr size_t NUM_SHIFTED_ENTITIES = 28; // The number of entities in DerivedWitnessEntities that are not going to be shifted. - static constexpr size_t NUM_DERIVED_WITNESS_ENTITIES_NON_SHIFTED = 1; + static constexpr size_t NUM_DERIVED_WITNESS_ENTITIES_NON_SHIFTED = 2; // Indices into the Shplemini commitments vector that identify which "to-be-shifted" witness commitments in the // unshifted block are duplicated in the shifted block, so their scalar muls can be merged. // @@ -110,18 +108,21 @@ class ECCVMFlavor { static_assert(NUM_MASKING_POLYNOMIALS == 1, "MaskingEntities size changed — review REPEATED_COMMITMENTS offset"); static_assert(REPEATED_COMMITMENTS.first.original_start == 64, "REPEATED_COMMITMENTS original_start changed — verify Shplemini offset convention"); - static_assert(REPEATED_COMMITMENTS.first.duplicate_start == 91, + static_assert(REPEATED_COMMITMENTS.first.duplicate_start == 94, "REPEATED_COMMITMENTS duplicate_start changed — verify Shplemini offset convention"); - static_assert(REPEATED_COMMITMENTS.first.count == 26, "REPEATED_COMMITMENTS count changed"); + static_assert(REPEATED_COMMITMENTS.first.count == 28, "REPEATED_COMMITMENTS count changed"); - using GrandProductRelations = std::tuple>; + using GrandProductRelations = + std::tuple, ECCVMSetScalarRelation, ECCVMSetMsmRelation>; // define the tuple of Relations that comprise the Sumcheck relation template using Relations_ = std::tuple, ECCVMPointTableRelation, ECCVMWnafRelation, ECCVMMSMRelation, - ECCVMSetRelation, + ECCVMSetWnafRelation, + ECCVMSetScalarRelation, + ECCVMSetMsmRelation, ECCVMLookupRelation, ECCVMBoolsRelation>; using Relations = Relations_; @@ -207,8 +208,11 @@ class ECCVMFlavor { */ template struct DerivedWitnessEntities { DEFINE_FLAVOR_MEMBERS(DataType, - z_perm, // column 0 - lookup_inverses); // column 1 + z_perm, // column 0 (shifted) + z_perm_scalar, // column 1 (shifted) + z_perm_msm, // column 2 (shifted) + den_wnaf_partial, // column 3 (non-shifted) + lookup_inverses); // column 4 (non-shifted) }; template class WireNonShiftedEntities { public: @@ -392,7 +396,9 @@ class ECCVMFlavor { transcript_accumulator_not_empty_shift, // column 22 transcript_accumulator_x_shift, // column 23 transcript_accumulator_y_shift, // column 24 - z_perm_shift); // column 25 + z_perm_shift, // column 25 + z_perm_scalar_shift, // column 26 + z_perm_msm_shift); // column 27 }; template @@ -424,7 +430,9 @@ class ECCVMFlavor { entities.transcript_accumulator_not_empty, // column 22 entities.transcript_accumulator_x, // column 23 entities.transcript_accumulator_y, // column 24 - entities.z_perm }; // column 25 + entities.z_perm, // column 25 + entities.z_perm_scalar, // column 26 + entities.z_perm_msm }; // column 27 } /** @@ -664,8 +672,10 @@ class ECCVMFlavor { poly = Polynomial(num_rows - 1, dyadic_num_rows, 1); } - // 3. z_perm: must stay full-size (grand product computed over unmasked_witness_size) + // 3. Grand product polynomials: must stay full-size (computed over unmasked_witness_size), shiftable z_perm = Polynomial(dyadic_num_rows - 1, dyadic_num_rows, 1); + z_perm_scalar = Polynomial(dyadic_num_rows - 1, dyadic_num_rows, 1); + z_perm_msm = Polynomial(dyadic_num_rows - 1, dyadic_num_rows, 1); // 4. Catch-all: precomputed, lookup_inverses, gemini_masking_poly → full size for (auto& poly : get_all()) { @@ -940,8 +950,13 @@ class ECCVMFlavor { Base::transcript_msm_count_zero_at_transition = "TRANSCRIPT_MSM_COUNT_ZERO_AT_TRANSITION"; Base::transcript_msm_count_at_transition_inverse = "TRANSCRIPT_MSM_COUNT_AT_TRANSITION_INVERSE"; Base::z_perm = "Z_PERM"; - Base::z_perm_shift = "Z_PERM_SHIFT"; + Base::z_perm_scalar = "Z_PERM_SCALAR"; + Base::z_perm_msm = "Z_PERM_MSM"; + Base::den_wnaf_partial = "DEN_WNAF_PARTIAL"; Base::lookup_inverses = "LOOKUP_INVERSES"; + Base::z_perm_shift = "Z_PERM_SHIFT"; + Base::z_perm_scalar_shift = "Z_PERM_SCALAR_SHIFT"; + Base::z_perm_msm_shift = "Z_PERM_MSM_SHIFT"; // The ones beginning with "__" are only used for debugging Base::lagrange_first = "__LAGRANGE_FIRST"; Base::lagrange_second = "__LAGRANGE_SECOND"; @@ -1049,6 +1064,10 @@ class ECCVMFlavor { // 4: We also force that `transcript_op==0`. return (polynomials.z_perm[edge_idx] == polynomials.z_perm_shift[edge_idx]) && (polynomials.z_perm[edge_idx + 1] == polynomials.z_perm_shift[edge_idx + 1]) && + (polynomials.z_perm_scalar[edge_idx] == polynomials.z_perm_scalar_shift[edge_idx]) && + (polynomials.z_perm_scalar[edge_idx + 1] == polynomials.z_perm_scalar_shift[edge_idx + 1]) && + (polynomials.z_perm_msm[edge_idx] == polynomials.z_perm_msm_shift[edge_idx]) && + (polynomials.z_perm_msm[edge_idx + 1] == polynomials.z_perm_msm_shift[edge_idx + 1]) && (polynomials.lagrange_last[edge_idx] == 0 && polynomials.lagrange_last[edge_idx + 1] == 0) && (polynomials.msm_transition[edge_idx] == 0 && polynomials.msm_transition[edge_idx + 1] == 0) && (polynomials.transcript_mul[edge_idx] == 0 && polynomials.transcript_mul[edge_idx + 1] == 0) && diff --git a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_prover.cpp b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_prover.cpp index 4cda958fdda6..ce2212ebe804 100644 --- a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_prover.cpp +++ b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_prover.cpp @@ -114,6 +114,43 @@ void ECCVMProver::execute_log_derivative_commitments_round() transcript->send_to_verifier(commitment_labels.lookup_inverses, key->commitment_key.commit(li) + key->commitment_key.commit(key->masking_tail_data.tails.lookup_inverses)); + + // Compute the intermediate committed polynomial den_wnaf_partial (product of first two wnaf denominator factors). + // This depends on beta/gamma, so it must be computed here (after challenges are received). + // den_wnaf_partial[i] = wnaf_out1 * wnaf_out2 where each wnaf_outK is: + // addK * (sliceK + gamma + (pc - count - (K-1)) * beta + round * beta_sqr + tag) + (1 - addK) + { + auto& polys = key->polynomials; + const auto& gamma = relation_parameters.gamma; + const auto& beta_val = relation_parameters.beta; + const auto& beta_sqr_val = relation_parameters.beta_sqr; + const auto& beta_quartic_val = relation_parameters.beta_quartic; + const auto first_term_tag = beta_quartic_val * ECCVMSetRelationConstants::FIRST_TERM_TAG; + + for (size_t i = 0; i < unmasked_witness_size; ++i) { + const auto msm_pc_val = polys.msm_pc[i]; + const auto msm_count_val = polys.msm_count[i]; + const auto msm_round_val = polys.msm_round[i]; + + const auto add1_val = polys.msm_add1[i]; + const auto slice1_val = polys.msm_slice1[i]; + auto wnaf_out1 = add1_val * (slice1_val + gamma + (msm_pc_val - msm_count_val) * beta_val + + msm_round_val * beta_sqr_val + first_term_tag) + + (-add1_val + 1); + + const auto add2_val = polys.msm_add2[i]; + const auto slice2_val = polys.msm_slice2[i]; + auto wnaf_out2 = add2_val * (slice2_val + gamma + (msm_pc_val - msm_count_val - 1) * beta_val + + msm_round_val * beta_sqr_val + first_term_tag) + + (-add2_val + 1); + + polys.den_wnaf_partial.at(i) = wnaf_out1 * wnaf_out2; + } + } + auto& dwp = key->polynomials.den_wnaf_partial; + transcript->send_to_verifier(commitment_labels.den_wnaf_partial, + key->commitment_key.commit(dwp) + + key->commitment_key.commit(key->masking_tail_data.tails.den_wnaf_partial)); } /** @@ -125,10 +162,21 @@ void ECCVMProver::execute_grand_product_computation_round() BB_BENCH_NAME("ECCVMProver::execute_grand_product_computation_round"); // Compute permutation grand product and their commitments compute_grand_products(key->polynomials, relation_parameters, unmasked_witness_size); + auto& zp = key->polynomials.z_perm; transcript->send_to_verifier(commitment_labels.z_perm, key->commitment_key.commit(zp) + key->commitment_key.commit(key->masking_tail_data.tails.z_perm)); + + auto& zps = key->polynomials.z_perm_scalar; + transcript->send_to_verifier(commitment_labels.z_perm_scalar, + key->commitment_key.commit(zps) + + key->commitment_key.commit(key->masking_tail_data.tails.z_perm_scalar)); + + auto& zpm = key->polynomials.z_perm_msm; + transcript->send_to_verifier(commitment_labels.z_perm_msm, + key->commitment_key.commit(zpm) + + key->commitment_key.commit(key->masking_tail_data.tails.z_perm_msm)); } /** diff --git a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_relation_corruption.test.cpp b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_relation_corruption.test.cpp index 9baf1f3fb599..4170dc1bde9e 100644 --- a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_relation_corruption.test.cpp +++ b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_relation_corruption.test.cpp @@ -97,7 +97,7 @@ ProverPolynomials build_valid_eccvm_msm_state() /** * @brief Compute random Fiat-Shamir challenges and derived polynomials (logderivative inverse, grand product) - * needed to check ECCVMSetRelation and ECCVMLookupRelation. + * needed to check ECCVMSetWnaf/Scalar/MsmRelation and ECCVMLookupRelation. */ RelationParameters compute_full_relation_params(ProverPolynomials& polynomials) { @@ -105,8 +105,11 @@ RelationParameters compute_full_relation_params(ProverPolynomials& polynomia const FF gamma = FF::random_element(&engine); const FF beta_sqr = beta.sqr(); const FF beta_cube = beta_sqr * beta; - auto eccvm_set_permutation_delta = - gamma * (gamma + beta_sqr) * (gamma + beta_sqr + beta_sqr) * (gamma + beta_sqr + beta_sqr + beta_sqr); + const FF beta_quartic = beta_sqr * beta_sqr; + auto first_term_tag = beta_quartic; // FIRST_TERM_TAG (= 1) * beta_quartic + auto eccvm_set_permutation_delta = (gamma + first_term_tag) * (gamma + beta_sqr + first_term_tag) * + (gamma + beta_sqr + beta_sqr + first_term_tag) * + (gamma + beta_sqr + beta_sqr + beta_sqr + first_term_tag); eccvm_set_permutation_delta = eccvm_set_permutation_delta.invert(); RelationParameters params{ @@ -116,14 +119,44 @@ RelationParameters compute_full_relation_params(ProverPolynomials& polynomia .public_input_delta = 0, .beta_sqr = beta_sqr, .beta_cube = beta_cube, + .beta_quartic = beta_quartic, .eccvm_set_permutation_delta = eccvm_set_permutation_delta, }; const size_t num_rows = polynomials.get_polynomial_size(); const size_t unmasked_witness_size = num_rows - NUM_DISABLED_ROWS_IN_SUMCHECK; compute_logderivative_inverse>(polynomials, params, unmasked_witness_size); - compute_grand_product>(polynomials, params, unmasked_witness_size); + + // Compute den_wnaf_partial before wnaf grand product + { + const auto first_term_tag_val = beta_quartic * ECCVMSetRelationConstants::FIRST_TERM_TAG; + for (size_t i = 0; i < unmasked_witness_size; ++i) { + const auto msm_pc_val = polynomials.msm_pc[i]; + const auto msm_count_val = polynomials.msm_count[i]; + const auto msm_round_val = polynomials.msm_round[i]; + + const auto add1_val = polynomials.msm_add1[i]; + const auto slice1_val = polynomials.msm_slice1[i]; + auto wnaf_out1 = add1_val * (slice1_val + gamma + (msm_pc_val - msm_count_val) * beta + + msm_round_val * beta_sqr + first_term_tag_val) + + (-add1_val + 1); + + const auto add2_val = polynomials.msm_add2[i]; + const auto slice2_val = polynomials.msm_slice2[i]; + auto wnaf_out2 = add2_val * (slice2_val + gamma + (msm_pc_val - msm_count_val - 1) * beta + + msm_round_val * beta_sqr + first_term_tag_val) + + (-add2_val + 1); + + polynomials.den_wnaf_partial.at(i) = wnaf_out1 * wnaf_out2; + } + } + + compute_grand_product>(polynomials, params, unmasked_witness_size); + compute_grand_product>(polynomials, params, unmasked_witness_size); + compute_grand_product>(polynomials, params, unmasked_witness_size); polynomials.z_perm_shift = Polynomial(polynomials.z_perm.shifted()); + polynomials.z_perm_scalar_shift = Polynomial(polynomials.z_perm_scalar.shifted()); + polynomials.z_perm_msm_shift = Polynomial(polynomials.z_perm_msm.shifted()); return params; } @@ -310,7 +343,7 @@ TEST_F(ECCVMRelationCorruptionTests, MSMRelationFailsOnShiftedMSMTable) // Verify that all other ECCVM relations still pass after the shift. // We compute random Fiat-Shamir challenges and derived polynomials (logderivative inverse, grand product) - // so we can also check ECCVMSetRelation and ECCVMLookupRelation. + // so we can also check ECCVMSetWnaf/Scalar/MsmRelation and ECCVMLookupRelation. auto full_params = compute_full_relation_params(polynomials); // Relations that don't touch MSM columns should be completely unaffected. @@ -330,13 +363,12 @@ TEST_F(ECCVMRelationCorruptionTests, MSMRelationFailsOnShiftedMSMTable) RelationChecker::check>(polynomials, full_params, "ECCVMBoolsRelation"); EXPECT_TRUE(bools_failures.empty()) << "ECCVMBoolsRelation should still pass"; - // The Set relation enforces a multiset equality between MSM output tuples (pc, acc_x, acc_y, msm_size) + // The MSM set relation enforces a multiset equality between MSM output tuples (pc, acc_x, acc_y, msm_size) // and the transcript. Shifting the MSM columns corrupts these tuples, so the grand product (computed - // post-shift) reflects mismatched reads/writes and the relation correctly fails. It is possible that with more - // care, we could make this also pass. - auto set_failures = - RelationChecker::check>(polynomials, full_params, "ECCVMSetRelation"); - EXPECT_FALSE(set_failures.empty()) << "ECCVMSetRelation should also fail (MSM output tuples are shifted)"; + // post-shift) reflects mismatched reads/writes and the relation correctly fails. + auto set_msm_failures = + RelationChecker::check>(polynomials, full_params, "ECCVMSetMsmRelation"); + EXPECT_FALSE(set_msm_failures.empty()) << "ECCVMSetMsmRelation should also fail (MSM output tuples are shifted)"; // The Lookup relation's logderivative inverse is computed post-shift, so it adapts to the // shifted column values. The per-row subrelation passes, and the sum-over-trace (linearly @@ -392,9 +424,9 @@ TEST_F(ECCVMRelationCorruptionTests, SetRelationFailsOnZPermNonZeroAtFirstRow) auto polynomials = build_valid_eccvm_msm_state(); auto params = compute_full_relation_params(polynomials); - // Baseline: set relation passes - auto baseline = RelationChecker::check>(polynomials, params, "ECCVMSetRelation"); - EXPECT_TRUE(baseline.empty()) << "Baseline set relation should pass"; + // Baseline: wnaf set relation passes + auto baseline = RelationChecker::check>(polynomials, params, "ECCVMSetWnafRelation"); + EXPECT_TRUE(baseline.empty()) << "Baseline wnaf set relation should pass"; // Derive expected lagrange_first position from z_perm shiftable structure ASSERT_TRUE(polynomials.z_perm.is_shiftable()); @@ -426,11 +458,11 @@ TEST_F(ECCVMRelationCorruptionTests, SetRelationFailsOnZPermNonZeroAtFirstRow) // Tamper: set z_perm to non-zero where lagrange_first is active polynomials.z_perm.at(first_row) = FF(1); - auto failures = RelationChecker::check>( - polynomials, params, "ECCVMSetRelation - After setting z_perm != 0 at lagrange_first"); + auto failures = RelationChecker::check>( + polynomials, params, "ECCVMSetWnafRelation - After setting z_perm != 0 at lagrange_first"); EXPECT_FALSE(failures.empty()) << "Set relation should fail after z_perm init corruption"; - EXPECT_TRUE(failures.contains(ECCVMSetRelationImpl::Z_PERM_INIT)) + EXPECT_TRUE(failures.contains(ECCVMSetWnafRelationImpl::Z_PERM_INIT)) << "Sub-relation Z_PERM_INIT should catch the corruption"; - EXPECT_EQ(failures.at(ECCVMSetRelationImpl::Z_PERM_INIT), first_row) + EXPECT_EQ(failures.at(ECCVMSetWnafRelationImpl::Z_PERM_INIT), first_row) << "Failure should be at lagrange_first row"; } diff --git a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_trace_checker.cpp b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_trace_checker.cpp index bc78b150ae2c..c6d24f077d7e 100644 --- a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_trace_checker.cpp +++ b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_trace_checker.cpp @@ -46,9 +46,38 @@ bool ECCVMTraceChecker::check(Builder& builder, const size_t num_rows = polynomials.get_polynomial_size(); const size_t unmasked_witness_size = num_rows - NUM_DISABLED_ROWS_IN_SUMCHECK; compute_logderivative_inverse>(polynomials, params, unmasked_witness_size); - compute_grand_product>(polynomials, params, unmasked_witness_size); + + // Compute den_wnaf_partial before the wnaf grand product (it reads this polynomial) + { + const auto first_term_tag_val = beta_quartic * ECCVMSetRelationConstants::FIRST_TERM_TAG; + for (size_t i = 0; i < unmasked_witness_size; ++i) { + const auto msm_pc_val = polynomials.msm_pc[i]; + const auto msm_count_val = polynomials.msm_count[i]; + const auto msm_round_val = polynomials.msm_round[i]; + + const auto add1_val = polynomials.msm_add1[i]; + const auto slice1_val = polynomials.msm_slice1[i]; + auto wnaf_out1 = add1_val * (slice1_val + gamma + (msm_pc_val - msm_count_val) * beta + + msm_round_val * beta_sqr + first_term_tag_val) + + (-add1_val + 1); + + const auto add2_val = polynomials.msm_add2[i]; + const auto slice2_val = polynomials.msm_slice2[i]; + auto wnaf_out2 = add2_val * (slice2_val + gamma + (msm_pc_val - msm_count_val - 1) * beta + + msm_round_val * beta_sqr + first_term_tag_val) + + (-add2_val + 1); + + polynomials.den_wnaf_partial.at(i) = wnaf_out1 * wnaf_out2; + } + } + + compute_grand_product>(polynomials, params, unmasked_witness_size); + compute_grand_product>(polynomials, params, unmasked_witness_size); + compute_grand_product>(polynomials, params, unmasked_witness_size); polynomials.z_perm_shift = Polynomial(polynomials.z_perm.shifted()); + polynomials.z_perm_scalar_shift = Polynomial(polynomials.z_perm_scalar.shifted()); + polynomials.z_perm_msm_shift = Polynomial(polynomials.z_perm_msm.shifted()); const auto evaluate_relation = [&](const std::string& relation_name) { typename Relation::SumcheckArrayOfValuesOverSubrelations result; @@ -94,7 +123,9 @@ bool ECCVMTraceChecker::check(Builder& builder, result = result && evaluate_relation.template operator()>("ECCVMPointTableRelation"); result = result && evaluate_relation.template operator()>("ECCVMWnafRelation"); result = result && evaluate_relation.template operator()>("ECCVMMSMRelation"); - result = result && evaluate_relation.template operator()>("ECCVMSetRelation"); + result = result && evaluate_relation.template operator()>("ECCVMSetWnafRelation"); + result = result && evaluate_relation.template operator()>("ECCVMSetScalarRelation"); + result = result && evaluate_relation.template operator()>("ECCVMSetMsmRelation"); result = result && evaluate_relation.template operator()>("ECCVMBoolsRelation"); using LookupRelation = ECCVMLookupRelation; diff --git a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_verifier.cpp b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_verifier.cpp index 9b7d179788ac..cc8b0898f2bc 100644 --- a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_verifier.cpp +++ b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_verifier.cpp @@ -65,7 +65,13 @@ typename ECCVMVerifier_::ReductionResult ECCVMVerifier_::reduce_ // Get commitment to permutation and lookup grand products commitments.lookup_inverses = transcript->template receive_from_prover(commitment_labels.lookup_inverses); + commitments.den_wnaf_partial = + transcript->template receive_from_prover(commitment_labels.den_wnaf_partial); commitments.z_perm = transcript->template receive_from_prover(commitment_labels.z_perm); + commitments.z_perm_scalar = + transcript->template receive_from_prover(commitment_labels.z_perm_scalar); + commitments.z_perm_msm = + transcript->template receive_from_prover(commitment_labels.z_perm_msm); // Each linearly independent subrelation contribution is multiplied by `alpha^i`, where // i = 0, ..., NUM_SUBRELATIONS- 1. diff --git a/barretenberg/cpp/src/barretenberg/relations/ecc_vm/ecc_set_relation.cpp b/barretenberg/cpp/src/barretenberg/relations/ecc_vm/ecc_set_relation.cpp index 4e4a344d7e29..3b87e5e08724 100644 --- a/barretenberg/cpp/src/barretenberg/relations/ecc_vm/ecc_set_relation.cpp +++ b/barretenberg/cpp/src/barretenberg/relations/ecc_vm/ecc_set_relation.cpp @@ -9,8 +9,14 @@ #include "ecc_set_relation_impl.hpp" namespace bb { -template class ECCVMSetRelationImpl; -DEFINE_SUMCHECK_RELATION_CLASS(ECCVMSetRelationImpl, ECCVMFlavor); -DEFINE_SUMCHECK_PERMUTATION_CLASS(ECCVMSetRelationImpl, ECCVMFlavor); +template class ECCVMSetWnafRelationImpl; +template class ECCVMSetScalarRelationImpl; +template class ECCVMSetMsmRelationImpl; +DEFINE_SUMCHECK_RELATION_CLASS(ECCVMSetWnafRelationImpl, ECCVMFlavor); +DEFINE_SUMCHECK_RELATION_CLASS(ECCVMSetScalarRelationImpl, ECCVMFlavor); +DEFINE_SUMCHECK_RELATION_CLASS(ECCVMSetMsmRelationImpl, ECCVMFlavor); +DEFINE_SUMCHECK_PERMUTATION_CLASS(ECCVMSetWnafRelationImpl, ECCVMFlavor); +DEFINE_SUMCHECK_PERMUTATION_CLASS(ECCVMSetScalarRelationImpl, ECCVMFlavor); +DEFINE_SUMCHECK_PERMUTATION_CLASS(ECCVMSetMsmRelationImpl, ECCVMFlavor); } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/relations/ecc_vm/ecc_set_relation.hpp b/barretenberg/cpp/src/barretenberg/relations/ecc_vm/ecc_set_relation.hpp index 30486e28c99e..2baf1572ee1e 100644 --- a/barretenberg/cpp/src/barretenberg/relations/ecc_vm/ecc_set_relation.hpp +++ b/barretenberg/cpp/src/barretenberg/relations/ecc_vm/ecc_set_relation.hpp @@ -14,68 +14,170 @@ namespace bb { -template class ECCVMSetRelationImpl { - public: - using FF = FF_; - +/** + * @brief Shared utilities for the three ECCVM set relation grand products. + * @details The original single ECCVMSetRelation has been split into three independent grand products + * to reduce the maximum relation degree from 22 to 9 (see #2226). + */ +struct ECCVMSetRelationConstants { // Domain separation tags for the three tuple families in the set relation grand product. - // Each tuple family uses a distinct tag multiplied by beta^4 to prevent cross-family collisions. - // Without these tags, tuples from different families with identical packed values would produce - // identical fingerprints, allowing cross-family substitution in the multiset equality check. static constexpr uint64_t FIRST_TERM_TAG = 1; // (pc, round, wnaf_slice) static constexpr uint64_t SECOND_TERM_TAG = 2; // (pc, P.x, P.y, scalar) static constexpr uint64_t THIRD_TERM_TAG = 3; // (pc, P.x, P.y, msm_size) - // Named subrelation indices — matches SUBRELATION_PARTIAL_LENGTHS ordering. + template static Accumulator convert_to_wnaf(const auto& s0, const auto& s1) + { + auto t = s0 + s0; + t += t; + t += s1; + auto naf = t + t - 15; + return naf; + } +}; + +/** + * @brief Grand product #1: validates multiset equality for (pc, round, wnaf_slice) tuples. + * @details Numerator source: Precompute table (ECCVMWnafRelation). + * Denominator source: MSM table (ECCVMMSMRelation). + * Uses an intermediate committed polynomial `den_wnaf_partial` to cache the product of + * the first two denominator factors, reducing the grand product degree from 10 to 9. + * + * Subrelation partial lengths: {9, 3, 3, 5} + * [0] Grand product: (z_perm + L_first) * NUM - (z_perm_shift + L_last) * DEN + * [1] Left-shiftable: L_last * z_perm_shift + * [2] z_perm initialization: L_first * z_perm + * [3] den_wnaf_partial: den_wnaf_partial - wnaf_out1 * wnaf_out2 + */ +template class ECCVMSetWnafRelationImpl { + public: + using FF = FF_; + enum SubrelationIndex : size_t { GRAND_PRODUCT = 0, LEFT_SHIFTABLE = 1, Z_PERM_INIT = 2, + DEN_PARTIAL_CONSTRAINT = 3, NUM_SUBRELATIONS, }; - static constexpr std::array SUBRELATION_PARTIAL_LENGTHS{ - 22, // grand product construction sub-relation - 3, // left-shiftable polynomial sub-relation - 3 // z_perm initialization sub-relation + static constexpr std::array SUBRELATION_PARTIAL_LENGTHS{ + 9, // grand product construction sub-relation + 3, // left-shiftable polynomial sub-relation + 3, // z_perm initialization sub-relation + 5 // den_wnaf_partial constraint sub-relation }; static_assert(NUM_SUBRELATIONS == SUBRELATION_PARTIAL_LENGTHS.size()); - // prover optimization to allow for skipping the computation of sub-relations at certain points in sumcheck. + template inline static bool skip(const AllEntities& in) { - // For the first accumulator in the set relation, the added-on term is 0 if the following vanishes: - // - // `(z_perm + lagrange_first) * numerator_evaluation - (z_perm_shift + lagrange_last) * denominator_evaluation`, - // - // i.e., if z_perm is well-formed. - // - // For the second accumulator in the set relation, the added-on term is 0 if the following vanishes: - // - // `lagrange_last_short * z_perm_shift_short` - // - // To know when we can skip this computation (i.e., when it is "automatically" 0), most cases are handled by the - // condtion `z_perm == z_perf_shift`. In most circumstances, this implies that with overwhelming probability, - // none of the wire values for the present input are involved in non-trivial copy constraints. - // - // There are two other edge-cases we need to check for to know we can skip the computation. First, - // `transcript_mul` can be 1 even though the multiplication is "degenerate" (and not handled by the MSM table): - // this holds if either the scalar is 0 or the point is the neutral element. Therefore we force this. Second, we - // must force lagrange_last == 0. - return (in.z_perm - in.z_perm_shift).is_zero() && in.transcript_mul.is_zero() && in.lagrange_last.is_zero(); + return (in.z_perm - in.z_perm_shift).is_zero() && in.lagrange_last.is_zero(); } - template static Accumulator convert_to_wnaf(const auto& s0, const auto& s1) + inline static auto& get_grand_product_polynomial(auto& input) { return input.z_perm; } + inline static auto& get_shifted_grand_product_polynomial(auto& input) { return input.z_perm_shift; } + + template + static Accumulator compute_grand_product_numerator(const AllEntities& in, const Parameters& params); + + template + static Accumulator compute_grand_product_denominator(const AllEntities& in, const Parameters& params); + + template + static void accumulate(ContainerOverSubrelations& accumulator, + const AllEntities& in, + const Parameters& params, + const FF& scaling_factor); +}; + +template using ECCVMSetWnafRelation = Relation>; + +/** + * @brief Grand product #2: validates multiset equality for (pc, P.x, P.y, scalar) tuples. + * @details Numerator source: Precompute table (ECCVMWnafRelation, ECCVMPointTableRelation). + * Denominator source: Transcript table (ECCVMTranscriptRelation). + * + * Subrelation partial lengths: {8, 3, 3} + * [0] Grand product: (z_perm_scalar + L_first) * NUM - (z_perm_scalar_shift + L_last) * DEN + * [1] Left-shiftable: L_last * z_perm_scalar_shift + * [2] z_perm initialization: L_first * z_perm_scalar + */ +template class ECCVMSetScalarRelationImpl { + public: + using FF = FF_; + + enum SubrelationIndex : size_t { + GRAND_PRODUCT = 0, + LEFT_SHIFTABLE = 1, + Z_PERM_INIT = 2, + NUM_SUBRELATIONS, + }; + + static constexpr std::array SUBRELATION_PARTIAL_LENGTHS{ + 8, // grand product construction sub-relation + 3, // left-shiftable polynomial sub-relation + 3 // z_perm initialization sub-relation + }; + static_assert(NUM_SUBRELATIONS == SUBRELATION_PARTIAL_LENGTHS.size()); + + template inline static bool skip(const AllEntities& in) { - auto t = s0 + s0; - t += t; - t += s1; + return (in.z_perm_scalar - in.z_perm_scalar_shift).is_zero() && in.transcript_mul.is_zero() && + in.lagrange_last.is_zero(); + } - auto naf = t + t - 15; - return naf; + inline static auto& get_grand_product_polynomial(auto& input) { return input.z_perm_scalar; } + inline static auto& get_shifted_grand_product_polynomial(auto& input) { return input.z_perm_scalar_shift; } + + template + static Accumulator compute_grand_product_numerator(const AllEntities& in, const Parameters& params); + + template + static Accumulator compute_grand_product_denominator(const AllEntities& in, const Parameters& params); + + template + static void accumulate(ContainerOverSubrelations& accumulator, + const AllEntities& in, + const Parameters& params, + const FF& scaling_factor); +}; + +template using ECCVMSetScalarRelation = Relation>; + +/** + * @brief Grand product #3: validates multiset equality for (pc, P.x, P.y, msm_size) tuples. + * @details Numerator source: MSM table (ECCVMMSMRelation). + * Denominator source: Transcript table (ECCVMTranscriptRelation). + * + * Subrelation partial lengths: {6, 3, 3} + * [0] Grand product: (z_perm_msm + L_first) * NUM - (z_perm_msm_shift + L_last) * DEN + * [1] Left-shiftable: L_last * z_perm_msm_shift + * [2] z_perm initialization: L_first * z_perm_msm + */ +template class ECCVMSetMsmRelationImpl { + public: + using FF = FF_; + + enum SubrelationIndex : size_t { + GRAND_PRODUCT = 0, + LEFT_SHIFTABLE = 1, + Z_PERM_INIT = 2, + NUM_SUBRELATIONS, + }; + + static constexpr std::array SUBRELATION_PARTIAL_LENGTHS{ + 6, // grand product construction sub-relation + 3, // left-shiftable polynomial sub-relation + 3 // z_perm initialization sub-relation + }; + static_assert(NUM_SUBRELATIONS == SUBRELATION_PARTIAL_LENGTHS.size()); + + template inline static bool skip(const AllEntities& in) + { + return (in.z_perm_msm - in.z_perm_msm_shift).is_zero() && in.lagrange_last.is_zero(); } - inline static auto& get_grand_product_polynomial(auto& input) { return input.z_perm; } - inline static auto& get_shifted_grand_product_polynomial(auto& input) { return input.z_perm_shift; } + inline static auto& get_grand_product_polynomial(auto& input) { return input.z_perm_msm; } + inline static auto& get_shifted_grand_product_polynomial(auto& input) { return input.z_perm_msm_shift; } template static Accumulator compute_grand_product_numerator(const AllEntities& in, const Parameters& params); @@ -90,6 +192,6 @@ template class ECCVMSetRelationImpl { const FF& scaling_factor); }; -template using ECCVMSetRelation = Relation>; +template using ECCVMSetMsmRelation = Relation>; } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/relations/ecc_vm/ecc_set_relation_impl.hpp b/barretenberg/cpp/src/barretenberg/relations/ecc_vm/ecc_set_relation_impl.hpp index bbed4bb5457f..455e21f743fb 100644 --- a/barretenberg/cpp/src/barretenberg/relations/ecc_vm/ecc_set_relation_impl.hpp +++ b/barretenberg/cpp/src/barretenberg/relations/ecc_vm/ecc_set_relation_impl.hpp @@ -11,66 +11,18 @@ namespace bb { -/** - * @brief Performs multiset equality checks for the ECCVM. This faciliates "communication" between disjoint sets of - * columns, which we view as tables: the Precomputed table, the MSM table, and the Transcript table. This used to be - * called a strict lookup argument (where every element written was read _exactly_ once.) - * - * @details ECCVMSetRelationImpl validates the correctness of the "inputs"/"outputs" of the three main algorithms - * evaluated by the ECCVM. Note that the terminology of "inputs" and "outputs" is _purely psychological_; they each just - * name the multiset we are adding to. We alternately use the terminology "numerator source" and "denominator source". - * - * It will be helpful to recall that `pc` always stands for point-counter. We use the terms interchangably. - * - * FIRST TERM: tuple of (pc, round, wnaf_slice), computed when slicing scalar multipliers into slices. - * - * Numerator source: Precompute table columns (constrained by ECCVMWnafRelation) - * Denominator source: MSM table columns (constrained by ECCVMMSMRelation) - * @note This ensures the following: - * * every WNAF slice computed during scalar decomposition must be used exactly once during the MSM computation. - * @warning There is a subtlety in this table, which slightly complicates the abstraction of multiset-equality testing. - * On the denominator side, when `addX == 0` for all `X ∈ {1, 2, 3, 4}` (automatically forced by `add1 == 0`), we - * multiply by 1. On the numerator side, to balance this out, this means that when `precompute_select == 0`, we must - * multiply by an additional `eccvm_set_permutation_delta`, which is the _inverse_ of the fingerprint of the tuple `(0, - * 0, 0)`. (This corresponds to "removing" the tuple `(0, 0, 0)` from the left multiset when `precompute_select == 0`). - * - * - * SECOND TERM: tuple of (pc, P.x, P.y, scalar-multiplier), linking scalar mul requests to their WNAF decompositions. - * - * Numerator source: Precompute table columns (constrained by ECCVMWnafRelation and ECCVMPointTableRelation) - * Denominator source: Transcript table columns (constrained by ECCVMTranscriptRelation) - * - * The numerator is gated by `precompute_point_transition == 1` (once per 128-bit scalar mul in the precompute table). - * The denominator is gated by `transcript_mul == 1` (once per MUL opcode in the transcript, which can contribute - * up to two 128-bit scalar muls via z1 and z2). - * @note This ensures the following: - * * every scalar multiplication requested in the transcript (with a non-zero scalar and non-infinity base point) - * has a corresponding entry in the precompute table. - * - * THIRD TERM: tuple of (pc, P.x, P.y, msm-size), linking MSM outputs to their claimed values in the transcript. - * - * Numerator source: MSM table columns (constrained by ECCVMMSMRelation) - * Denominator source: Transcript table columns (constrained by ECCVMTranscriptRelation) - * - * The numerator is gated by `msm_transition == 1` (once per completed MSM). - * The denominator is gated by `transcript_msm_transition == 1`. - * @note This ensures the following: - * * the MSM output computed in the MSM table matches the MSM output claimed in the transcript. - * * the MSM size and starting point counter are consistent between the two tables. - * - * - * @tparam FF - * @tparam AccumulatorTypes - * @param in - * @param relation_params - * @param index - * @return ECCVMSetRelationImpl::template Accumulator - */ +// ============================================================================================ +// Grand Product #1: WNAF slices — (pc, round, wnaf_slice) +// Numerator: Precompute table; Denominator: MSM table (via den_wnaf_partial intermediate) +// ============================================================================================ + template template -Accumulator ECCVMSetRelationImpl::compute_grand_product_numerator(const AllEntities& in, const Parameters& params) +Accumulator ECCVMSetWnafRelationImpl::compute_grand_product_numerator(const AllEntities& in, + const Parameters& params) { using View = typename Accumulator::View; + using Constants = ECCVMSetRelationConstants; const auto& precompute_round = View(in.precompute_round); const auto precompute_round2 = precompute_round + precompute_round; @@ -79,39 +31,19 @@ Accumulator ECCVMSetRelationImpl::compute_grand_product_numerator(const AllE const auto& gamma = params.gamma; const auto& beta = params.beta; const auto& beta_sqr = params.beta_sqr; - const auto& beta_cube = params.beta_cube; const auto& beta_quartic = params.beta_quartic; const auto& precompute_pc = View(in.precompute_pc); const auto& precompute_select = View(in.precompute_select); - // Domain separation: each tuple family includes a distinct tag * beta^4 term to prevent cross-family collisions. - const auto first_term_tag = beta_quartic * FIRST_TERM_TAG; - const auto second_term_tag = beta_quartic * SECOND_TERM_TAG; - const auto third_term_tag = beta_quartic * THIRD_TERM_TAG; - - /** - * @brief First term: tuple of (pc, round, wnaf_slice), computed when slicing scalar multipliers into slices, as - * part of ECCVMWnafRelation. - * - * @details - * There are 4 tuple entries per row of the Precompute table. Moreover, the element that "increments" is - * 4 * `precompute_round`, due to the fact that the Precompute columns contain four "digits"/slices per row. - * - * @note - * We only add this tuple if `precompute_select == 1`. Otherwise, we add a the tuple (0, 0, 0). - */ - - // OPTIMIZE(@zac-williamson #2226) optimize degrees + const auto first_term_tag = beta_quartic * Constants::FIRST_TERM_TAG; Accumulator numerator(1); // degree-0 { const auto& s0 = View(in.precompute_s1hi); const auto& s1 = View(in.precompute_s1lo); - auto wnaf_slice = s0 + s0; wnaf_slice += wnaf_slice; wnaf_slice += s1; - const auto wnaf_slice_input0 = wnaf_slice + gamma + precompute_pc * beta + precompute_round4 * beta_sqr + first_term_tag; numerator *= wnaf_slice_input0; // degree-1 @@ -119,11 +51,9 @@ Accumulator ECCVMSetRelationImpl::compute_grand_product_numerator(const AllE { const auto& s0 = View(in.precompute_s2hi); const auto& s1 = View(in.precompute_s2lo); - auto wnaf_slice = s0 + s0; wnaf_slice += wnaf_slice; wnaf_slice += s1; - const auto wnaf_slice_input1 = wnaf_slice + gamma + precompute_pc * beta + (precompute_round4 + 1) * beta_sqr + first_term_tag; numerator *= wnaf_slice_input1; // degree-2 @@ -131,11 +61,9 @@ Accumulator ECCVMSetRelationImpl::compute_grand_product_numerator(const AllE { const auto& s0 = View(in.precompute_s3hi); const auto& s1 = View(in.precompute_s3lo); - auto wnaf_slice = s0 + s0; wnaf_slice += wnaf_slice; wnaf_slice += s1; - const auto wnaf_slice_input2 = wnaf_slice + gamma + precompute_pc * beta + (precompute_round4 + 2) * beta_sqr + first_term_tag; numerator *= wnaf_slice_input2; // degree-3 @@ -143,7 +71,6 @@ Accumulator ECCVMSetRelationImpl::compute_grand_product_numerator(const AllE { const auto& s0 = View(in.precompute_s4hi); const auto& s1 = View(in.precompute_s4lo); - auto wnaf_slice = s0 + s0; wnaf_slice += wnaf_slice; wnaf_slice += s1; @@ -161,199 +88,45 @@ Accumulator ECCVMSetRelationImpl::compute_grand_product_numerator(const AllE numerator *= skew_input; // degree-6 } { - // in `EccvmProver` and `ECCVMVerifier`, we see that `eccvm_set_permutation_delta` is initially computed as - // (γ+t·β⁴)·(γ+β²+t·β⁴)·(γ+2β²+t·β⁴)·(γ+3β²+t·β⁴) (where t = FIRST_TERM_TAG) and _then_ inverted. const auto& eccvm_set_permutation_delta = params.eccvm_set_permutation_delta; - // if `precompute_select == 1`, don't change the numerator. if it is 0, then to get the grand product argument - // to work (as we have zero-padded the rows of the MSM table), we must multiply by the inverse of the - // fingerprint of (0, 0, 0). numerator *= precompute_select * (-eccvm_set_permutation_delta + 1) + eccvm_set_permutation_delta; // degree-7 } - - /** - * @brief Second term: tuple of (pc, P.x, P.y, scalar-multiplier), used in ECCVMWnafRelation and - * ECCVMPointTableRelation. - * - * @details - * ECCVMWnafRelation validates the sum of the wnaf slices associated with point-counter - * equals scalar-multiplier. ECCVMPointTableRelation computes a table of muliples of [P]: { -15[P], -13[P], ..., - * 15[P] }. We need to validate that the scalar-multiplier and [P] = (P.x, P.y) come from MUL opcodes in the - * transcript columns; in other words, that the wNAF expansion of the scalar-multiplier is correct. - * - * @note - * We only add the tuple to the multiset if `precompute_point_transition == 1`. - */ - { - const auto& table_x = View(in.precompute_tx); - const auto& table_y = View(in.precompute_ty); - - const auto& precompute_skew = View(in.precompute_skew); - const auto negative_inverse_seven = []() { - if constexpr (std::same_as) { - static constexpr FF negative_inverse_seven = FF(-7).invert(); - return negative_inverse_seven; - } else { - FF negative_inverse_seven = FF(-7).invert(); - return negative_inverse_seven; - } - }; - auto adjusted_skew = - precompute_skew * negative_inverse_seven(); // `precompute_skew` ∈ {0, 7}, `adjusted_skew`∈ {0, -1} - - const auto& wnaf_scalar_sum = View(in.precompute_scalar_sum); - const auto w0 = convert_to_wnaf(View(in.precompute_s1hi), View(in.precompute_s1lo)); - const auto w1 = convert_to_wnaf(View(in.precompute_s2hi), View(in.precompute_s2lo)); - const auto w2 = convert_to_wnaf(View(in.precompute_s3hi), View(in.precompute_s3lo)); - const auto w3 = convert_to_wnaf(View(in.precompute_s4hi), View(in.precompute_s4lo)); - - auto row_slice = w0; - row_slice += row_slice; - row_slice += row_slice; - row_slice += row_slice; - row_slice += row_slice; - row_slice += w1; - row_slice += row_slice; - row_slice += row_slice; - row_slice += row_slice; - row_slice += row_slice; - row_slice += w2; - row_slice += row_slice; - row_slice += row_slice; - row_slice += row_slice; - row_slice += row_slice; - row_slice += w3; // row_slice = 2^12 w_0 + 2^8 w_1 + 2^4 w_2 + 2^0 w_3 - - auto scalar_sum_full = wnaf_scalar_sum + wnaf_scalar_sum; - scalar_sum_full += scalar_sum_full; - scalar_sum_full += scalar_sum_full; - scalar_sum_full += scalar_sum_full; - scalar_sum_full += scalar_sum_full; - scalar_sum_full += scalar_sum_full; - scalar_sum_full += scalar_sum_full; - scalar_sum_full += scalar_sum_full; - scalar_sum_full += scalar_sum_full; - scalar_sum_full += scalar_sum_full; - scalar_sum_full += scalar_sum_full; - scalar_sum_full += scalar_sum_full; - scalar_sum_full += scalar_sum_full; - scalar_sum_full += scalar_sum_full; - scalar_sum_full += scalar_sum_full; - scalar_sum_full += scalar_sum_full; - scalar_sum_full += - row_slice + adjusted_skew; // scalar_sum_full = 2^16 * wnaf_scalar_sum + row_slice + adjusted_skew - - auto precompute_point_transition = View(in.precompute_point_transition); - - auto point_table_init_read = - (precompute_pc + table_x * beta + table_y * beta_sqr + scalar_sum_full * beta_cube + second_term_tag); - point_table_init_read = - precompute_point_transition * (point_table_init_read + gamma) + (-precompute_point_transition + 1); - - numerator *= point_table_init_read; // degree-9 - } - /** - * @brief Third term: tuple of (pc, P.x, P.y, msm-size) from ECCVMMSMRelation. - * @brief Third term: tuple of (pc, P.x, P.y, msm-size) from ECCVMMSMRelation. - * (P.x, P.y) is the output of a multi-scalar-multiplication evaluated in ECCVMMSMRelation. - * We need to validate that the same values (P.x, P.y) are present in the Transcript columns and describe a - * multi-scalar multiplication of size `msm-size`, starting at `pc`. - * multi-scalar multiplication of size `msm-size`, starting at `pc`. - * - * If `msm_transition_shift == 1`, this indicates the current row is the last row of a multiscalar - * multiplication evaluation. The output of the MSM will be present on `(msm_accumulator_x_shift, - * msm_accumulator_y_shift)`. The values of `msm_accumulator_x_shift, msm_accumulator_y_shift, msm_pc, - * msm_size_of_msm` must match up with equivalent values `transcript_msm_output_x, transcript_msm_output_y, - * transcript_pc, transcript_msm_count` present in the Transcript columns. - * - * Checking `msm_size` is correct (it is tied to the `pc`) is necessary to make sure the `msm_pc` increments - * correctly after it completes an MSM. - */ - { - const auto& lagrange_first = View(in.lagrange_first); - const auto& partial_msm_transition_shift = View(in.msm_transition_shift); - const auto msm_transition_shift = (-lagrange_first + 1) * partial_msm_transition_shift; - const auto& msm_pc_shift = View(in.msm_pc_shift); - - const auto& msm_x_shift = View(in.msm_accumulator_x_shift); - const auto& msm_y_shift = View(in.msm_accumulator_y_shift); - const auto& msm_size = View(in.msm_size_of_msm); - - // msm_transition = 1 when a row BEGINS a new msm - // - // row msm tx acc.x acc.y pc msm_size - // i 0 no no no yes - // i+1 1 yes yes yes no - // - // at row i we are at the final row of the current msm - // at row i the value of `msm_size` = size of current msm - // at row i + 1 we have the final accumulated value of the msm computation - // at row i + 1 we have updated `pc` to be `(pc at start of msm) + msm_count` - // at row i + 1 q_msm_transtiion = 1 - - auto msm_result_write = - msm_pc_shift + msm_x_shift * beta + msm_y_shift * beta_sqr + msm_size * beta_cube + third_term_tag; - - // msm_result_write = degree 2 - msm_result_write = msm_transition_shift * (msm_result_write + gamma) + (-msm_transition_shift + 1); - numerator *= msm_result_write; // degree-11 - } return numerator; } template template -Accumulator ECCVMSetRelationImpl::compute_grand_product_denominator(const AllEntities& in, const Parameters& params) +Accumulator ECCVMSetWnafRelationImpl::compute_grand_product_denominator(const AllEntities& in, + const Parameters& params) { using View = typename Accumulator::View; + using Constants = ECCVMSetRelationConstants; + (void)params; // beta/gamma are baked into den_wnaf_partial - // OPTIMIZE(@zac-williamson). The degree of this contribution is 17! makes overall relation degree 19. - // Can potentially optimize by refining the algebra. const auto& gamma = params.gamma; const auto& beta = params.beta; const auto& beta_sqr = params.beta_sqr; - const auto& beta_cube = params.beta_cube; const auto& beta_quartic = params.beta_quartic; const auto& msm_pc = View(in.msm_pc); const auto& msm_count = View(in.msm_count); const auto& msm_round = View(in.msm_round); - // Domain separation: must match the tags used in the numerator. - const auto first_term_tag = beta_quartic * FIRST_TERM_TAG; - const auto second_term_tag = beta_quartic * SECOND_TERM_TAG; - const auto third_term_tag = beta_quartic * THIRD_TERM_TAG; + const auto first_term_tag = beta_quartic * Constants::FIRST_TERM_TAG; - /** - * @brief First term: tuple of (pc, round, wnaf_slice), used to determine which points we extract from lookup tables - * when evaluaing MSMs in ECCVMMsmRelation. - * These values must be equivalent to the values computed in the 1st term of `compute_grand_product_numerator` - */ + // Use the committed intermediate polynomial for the first two wnaf output factors. + // This reduces the denominator degree from 8 to 5. Accumulator denominator(1); // degree-0 { - const auto& add1 = View(in.msm_add1); - const auto& msm_slice1 = View(in.msm_slice1); - - auto wnaf_slice_output1 = - add1 * (msm_slice1 + gamma + (msm_pc - msm_count) * beta + msm_round * beta_sqr + first_term_tag) + - (-add1 + 1); - denominator *= wnaf_slice_output1; // degree-2 - } - { - const auto& add2 = View(in.msm_add2); - const auto& msm_slice2 = View(in.msm_slice2); - - auto wnaf_slice_output2 = - add2 * (msm_slice2 + gamma + (msm_pc - msm_count - 1) * beta + msm_round * beta_sqr + first_term_tag) + - (-add2 + 1); - denominator *= wnaf_slice_output2; // degree-4 + const auto& den_partial = View(in.den_wnaf_partial); + denominator *= den_partial; // degree-1 } { const auto& add3 = View(in.msm_add3); const auto& msm_slice3 = View(in.msm_slice3); - auto wnaf_slice_output3 = add3 * (msm_slice3 + gamma + (msm_pc - msm_count - 2) * beta + msm_round * beta_sqr + first_term_tag) + (-add3 + 1); - denominator *= wnaf_slice_output3; // degree-6 + denominator *= wnaf_slice_output3; // degree-3 } { const auto& add4 = View(in.msm_add4); @@ -361,153 +134,374 @@ Accumulator ECCVMSetRelationImpl::compute_grand_product_denominator(const Al auto wnaf_slice_output4 = add4 * (msm_slice4 + gamma + (msm_pc - msm_count - 3) * beta + msm_round * beta_sqr + first_term_tag) + (-add4 + 1); - denominator *= wnaf_slice_output4; // degree-8 + denominator *= wnaf_slice_output4; // degree-5 } + return denominator; +} - /** - * @brief Second term: tuple of the form `(transcript_pc, transcript_Px, transcript_Py, z1)` OR `(transcript_pc, - * \beta * transcript_Px, -transcript_Py, z2)` for each scalar multiplication in ECCVMTranscriptRelation columns. - * Here \f$\beta\f$ is a cube root of unity in \f$\mathbb f_q\f$. These values must be equivalent to the second - * term values in `compute_grand_product_numerator` - * - * @details - * Recall that every element of \f$\mathbb F_r\f$ may be written as \f$z_1 + \zeta z_2 = z_1 - \beta z_2\f$, where - * the \f$z_i\f$ are 128 bit numbers and \f$\zeta = -\beta\f$ is a sixth root of unity. - */ +template +template +void ECCVMSetWnafRelationImpl::accumulate(ContainerOverSubrelations& accumulator, + const AllEntities& in, + const Parameters& params, + const FF& scaling_factor) +{ + using Constants = ECCVMSetRelationConstants; + + // Subrelation 0: Grand product { - const auto& transcript_pc = View(in.transcript_pc); - - const auto& transcript_Px = View(in.transcript_Px); - const auto& transcript_Py = View(in.transcript_Py); - const auto& z1 = View(in.transcript_z1); - const auto& z2 = View(in.transcript_z2); - const auto& z1_zero = View(in.transcript_z1zero); - const auto& z2_zero = View(in.transcript_z2zero); - const auto& base_infinity = View(in.transcript_base_infinity); - const auto& transcript_mul = View(in.transcript_mul); - - const auto& lookup_first = (-z1_zero + 1); - const auto& lookup_second = (-z2_zero + 1); - FF cube_root_unity = FF(bb::fq::cube_root_of_unity()); - - auto transcript_input1 = transcript_pc + transcript_Px * beta + transcript_Py * beta_sqr + z1 * beta_cube + - second_term_tag; // degree = 1 - auto transcript_input2 = (transcript_pc - lookup_first) + transcript_Px * cube_root_unity * beta - - transcript_Py * beta_sqr + z2 * beta_cube + second_term_tag; // degree = 2 - - // The following diagram expresses a fingerprint of part of the tuple. It does not include `transcript_pc` and - // has not weighted the X and Y with beta and beta_sqr respectively. The point is nonetheless to show exactly - // when a tuple is added to the multiset: iff it corresponds to a non-trivial (128-bit) scalar mul. If neither - // z1 nor z2 are zero, then we implicitly add _two_ tuples to the multiset. - // - // | q_mul | z2_zero | z1_zero | base_infinity | partial lookup | - // | ----- | ------- | ------- | ------------- |----------------------- | - // | 0 | - | - | - | 1 | - // | 1 | 0 | 0 | 0 | 1 | - // | 1 | 0 | 1 | 0 | X + gamma | - // | 1 | 1 | 0 | 0 | Y + gamma | - // | 1 | 1 | 1 | 0 | (X + gamma)(Y + gamma) | - // | 1 | 0 | 0 | 1 | 1 | - // | 1 | 0 | 1 | 1 | 1 | - // | 1 | 1 | 0 | 1 | 1 | - // | 1 | 1 | 1 | 1 | 1 | - transcript_input1 = (transcript_input1 + gamma) * lookup_first + (-lookup_first + 1); // degree 2 - transcript_input2 = (transcript_input2 + gamma) * lookup_second + (-lookup_second + 1); // degree 3 - - // transcript_product = degree 6 - auto transcript_product = (transcript_input1 * transcript_input2) * (-base_infinity + 1) + base_infinity; - - // point_table_init_write = degree 7 - auto point_table_init_write = transcript_mul * transcript_product + (-transcript_mul + 1); - denominator *= point_table_init_write; // degree 17 + using Accumulator = std::tuple_element_t; + using View = typename Accumulator::View; + + Accumulator numerator_evaluation = compute_grand_product_numerator(in, params); + Accumulator denominator_evaluation = compute_grand_product_denominator(in, params); + + const auto& lagrange_first = View(in.lagrange_first); + const auto& lagrange_last = View(in.lagrange_last); + const auto& z_perm = View(in.z_perm); + const auto& z_perm_shift = View(in.z_perm_shift); + + std::get(accumulator) += + ((z_perm + lagrange_first) * numerator_evaluation - + (z_perm_shift + lagrange_last) * denominator_evaluation) * + scaling_factor; } - /** - * @brief Third term: tuple of (pc, P.x, P.y, msm-size) from ECCVMTranscriptRelation. - * (P.x, P.y) is the *claimed* output of a multi-scalar-multiplication evaluated in ECCVMMSMRelation. - * We need to validate that the msm output produced in ECCVMMSMRelation is equivalent to the output present - * in `transcript_msm_output_x, transcript_msm_output_y`, for a given multi-scalar multiplication starting at - * `transcript_pc` and has size `transcript_msm_count`. - * @note In the case of an honest prover, `(transcript_msm_output_x, transcript_msm_output_y)` is the value of the - * just-completed MSM + `OFFSET` (as this is what the MSM table computes with to avoid branch logic.) - * - * in `transcript_msm_output_x, transcript_msm_output_y`, for a given multi-scalar multiplication starting at - * `transcript_pc` and has size `transcript_msm_count`. - * @note In the case of an honest prover, `(transcript_msm_output_x, transcript_msm_output_y)` is the value of the - * just-completed MSM + `OFFSET` (as this is what the MSM table computes with to avoid branch logic.) - * - */ + + // Subrelation 1: Left-shiftable (z_perm_shift = 0 at lagrange_last) { - const auto& transcript_pc_shift = View(in.transcript_pc_shift); - const auto& transcript_msm_x = View(in.transcript_msm_x); - const auto& transcript_msm_y = View(in.transcript_msm_y); - const auto& transcript_msm_transition = View(in.transcript_msm_transition); - const auto& transcript_msm_count = View(in.transcript_msm_count); - const auto& z1_zero = View(in.transcript_z1zero); - const auto& z2_zero = View(in.transcript_z2zero); - const auto& transcript_mul = View(in.transcript_mul); - const auto& base_infinity = View(in.transcript_base_infinity); - - // do not add to count if point at infinity! - auto full_msm_count = - transcript_msm_count + transcript_mul * ((-z1_zero + 1) + (-z2_zero + 1)) * (-base_infinity + 1); - // msm_result_read = degree 2 - auto msm_result_read = transcript_pc_shift + transcript_msm_x * beta + transcript_msm_y * beta_sqr + - full_msm_count * beta_cube + third_term_tag; - msm_result_read = transcript_msm_transition * (msm_result_read + gamma) + (-transcript_msm_transition + 1); - denominator *= msm_result_read; // degree-20 + using Accumulator = std::tuple_element_t; + using View = typename Accumulator::View; + std::get(accumulator) += View(in.lagrange_last) * View(in.z_perm_shift) * scaling_factor; } - return denominator; + + // Subrelation 2: z_perm initialization (z_perm = 0 at lagrange_first) + { + using Accumulator = std::tuple_element_t; + using View = typename Accumulator::View; + std::get(accumulator) += View(in.lagrange_first) * View(in.z_perm) * scaling_factor; + } + + // Subrelation 3: den_wnaf_partial constraint + // Constrains: den_wnaf_partial = wnaf_out1 * wnaf_out2 + { + using Accumulator = std::tuple_element_t; + using View = typename Accumulator::View; + + const auto& gamma = params.gamma; + const auto& beta = params.beta; + const auto& beta_sqr = params.beta_sqr; + const auto& beta_quartic = params.beta_quartic; + const auto& msm_pc = View(in.msm_pc); + const auto& msm_count = View(in.msm_count); + const auto& msm_round = View(in.msm_round); + const auto first_term_tag = beta_quartic * Constants::FIRST_TERM_TAG; + + const auto& add1 = View(in.msm_add1); + const auto& msm_slice1 = View(in.msm_slice1); + auto wnaf_out1 = + add1 * (msm_slice1 + gamma + (msm_pc - msm_count) * beta + msm_round * beta_sqr + first_term_tag) + + (-add1 + 1); + + const auto& add2 = View(in.msm_add2); + const auto& msm_slice2 = View(in.msm_slice2); + auto wnaf_out2 = + add2 * (msm_slice2 + gamma + (msm_pc - msm_count - 1) * beta + msm_round * beta_sqr + first_term_tag) + + (-add2 + 1); + + const auto& den_partial = View(in.den_wnaf_partial); + std::get(accumulator) += (den_partial - wnaf_out1 * wnaf_out2) * scaling_factor; + } +} + +// ============================================================================================ +// Grand Product #2: Scalar tuples — (pc, P.x, P.y, scalar) +// Numerator: Precompute table; Denominator: Transcript table +// ============================================================================================ + +template +template +Accumulator ECCVMSetScalarRelationImpl::compute_grand_product_numerator(const AllEntities& in, + const Parameters& params) +{ + using View = typename Accumulator::View; + using Constants = ECCVMSetRelationConstants; + + const auto& beta = params.beta; + const auto& beta_sqr = params.beta_sqr; + const auto& beta_cube = params.beta_cube; + const auto& beta_quartic = params.beta_quartic; + const auto& gamma = params.gamma; + const auto& precompute_pc = View(in.precompute_pc); + const auto& precompute_round = View(in.precompute_round); + const auto precompute_round2 = precompute_round + precompute_round; + const auto precompute_round4 = precompute_round2 + precompute_round2; + + const auto second_term_tag = beta_quartic * Constants::SECOND_TERM_TAG; + + const auto& table_x = View(in.precompute_tx); + const auto& table_y = View(in.precompute_ty); + + const auto& precompute_skew = View(in.precompute_skew); + const auto negative_inverse_seven = []() { + if constexpr (std::same_as) { + static constexpr FF negative_inverse_seven = FF(-7).invert(); + return negative_inverse_seven; + } else { + FF negative_inverse_seven = FF(-7).invert(); + return negative_inverse_seven; + } + }; + auto adjusted_skew = precompute_skew * negative_inverse_seven(); + + const auto& wnaf_scalar_sum = View(in.precompute_scalar_sum); + const auto w0 = Constants::convert_to_wnaf(View(in.precompute_s1hi), View(in.precompute_s1lo)); + const auto w1 = Constants::convert_to_wnaf(View(in.precompute_s2hi), View(in.precompute_s2lo)); + const auto w2 = Constants::convert_to_wnaf(View(in.precompute_s3hi), View(in.precompute_s3lo)); + const auto w3 = Constants::convert_to_wnaf(View(in.precompute_s4hi), View(in.precompute_s4lo)); + + auto row_slice = w0; + row_slice += row_slice; + row_slice += row_slice; + row_slice += row_slice; + row_slice += row_slice; + row_slice += w1; + row_slice += row_slice; + row_slice += row_slice; + row_slice += row_slice; + row_slice += row_slice; + row_slice += w2; + row_slice += row_slice; + row_slice += row_slice; + row_slice += row_slice; + row_slice += row_slice; + row_slice += w3; + + auto scalar_sum_full = wnaf_scalar_sum + wnaf_scalar_sum; + scalar_sum_full += scalar_sum_full; + scalar_sum_full += scalar_sum_full; + scalar_sum_full += scalar_sum_full; + scalar_sum_full += scalar_sum_full; + scalar_sum_full += scalar_sum_full; + scalar_sum_full += scalar_sum_full; + scalar_sum_full += scalar_sum_full; + scalar_sum_full += scalar_sum_full; + scalar_sum_full += scalar_sum_full; + scalar_sum_full += scalar_sum_full; + scalar_sum_full += scalar_sum_full; + scalar_sum_full += scalar_sum_full; + scalar_sum_full += scalar_sum_full; + scalar_sum_full += scalar_sum_full; + scalar_sum_full += scalar_sum_full; + scalar_sum_full += row_slice + adjusted_skew; + + auto precompute_point_transition = View(in.precompute_point_transition); + + auto point_table_init_read = + (precompute_pc + table_x * beta + table_y * beta_sqr + scalar_sum_full * beta_cube + second_term_tag); + point_table_init_read = + precompute_point_transition * (point_table_init_read + gamma) + (-precompute_point_transition + 1); + + return point_table_init_read; // degree-2 +} + +template +template +Accumulator ECCVMSetScalarRelationImpl::compute_grand_product_denominator(const AllEntities& in, + const Parameters& params) +{ + using View = typename Accumulator::View; + using Constants = ECCVMSetRelationConstants; + + const auto& gamma = params.gamma; + const auto& beta = params.beta; + const auto& beta_sqr = params.beta_sqr; + const auto& beta_cube = params.beta_cube; + const auto& beta_quartic = params.beta_quartic; + + const auto second_term_tag = beta_quartic * Constants::SECOND_TERM_TAG; + + const auto& transcript_pc = View(in.transcript_pc); + const auto& transcript_Px = View(in.transcript_Px); + const auto& transcript_Py = View(in.transcript_Py); + const auto& z1 = View(in.transcript_z1); + const auto& z2 = View(in.transcript_z2); + const auto& z1_zero = View(in.transcript_z1zero); + const auto& z2_zero = View(in.transcript_z2zero); + const auto& base_infinity = View(in.transcript_base_infinity); + const auto& transcript_mul = View(in.transcript_mul); + + const auto& lookup_first = (-z1_zero + 1); + const auto& lookup_second = (-z2_zero + 1); + FF cube_root_unity = FF(bb::fq::cube_root_of_unity()); + + auto transcript_input1 = + transcript_pc + transcript_Px * beta + transcript_Py * beta_sqr + z1 * beta_cube + second_term_tag; + auto transcript_input2 = (transcript_pc - lookup_first) + transcript_Px * cube_root_unity * beta - + transcript_Py * beta_sqr + z2 * beta_cube + second_term_tag; + + transcript_input1 = (transcript_input1 + gamma) * lookup_first + (-lookup_first + 1); // degree 2 + transcript_input2 = (transcript_input2 + gamma) * lookup_second + (-lookup_second + 1); // degree 2 + + auto transcript_product = (transcript_input1 * transcript_input2) * (-base_infinity + 1) + base_infinity; // deg 5 + + auto point_table_init_write = transcript_mul * transcript_product + (-transcript_mul + 1); // degree 6 + return point_table_init_write; } -/** - * @brief Expression for the standard arithmetic gate. - * @dbetails The relation is defined as C(in(X)...) = - * (q_m * w_r * w_l) + (q_l * w_l) + (q_r * w_r) + (q_o * w_o) + q_c - * - * @param evals transformed to `evals + C(in(X)...)*scaling_factor` - * @param in an std::array containing the fully extended Accumulator edges. - * @param parameters contains beta, gamma, and public_input_delta, .... - * @param scaling_factor optional term to scale the evaluation before adding to evals. - */ template template -void ECCVMSetRelationImpl::accumulate(ContainerOverSubrelations& accumulator, - const AllEntities& in, - const Parameters& params, - const FF& scaling_factor) +void ECCVMSetScalarRelationImpl::accumulate(ContainerOverSubrelations& accumulator, + const AllEntities& in, + const Parameters& params, + const FF& scaling_factor) +{ + // Subrelation 0: Grand product + { + using Accumulator = std::tuple_element_t; + using View = typename Accumulator::View; + + Accumulator numerator_evaluation = compute_grand_product_numerator(in, params); + Accumulator denominator_evaluation = compute_grand_product_denominator(in, params); + + const auto& lagrange_first = View(in.lagrange_first); + const auto& lagrange_last = View(in.lagrange_last); + const auto& z_perm = View(in.z_perm_scalar); + const auto& z_perm_shift = View(in.z_perm_scalar_shift); + + std::get(accumulator) += + ((z_perm + lagrange_first) * numerator_evaluation - + (z_perm_shift + lagrange_last) * denominator_evaluation) * + scaling_factor; + } + + // Subrelation 1: Left-shiftable + { + using Accumulator = std::tuple_element_t; + using View = typename Accumulator::View; + std::get(accumulator) += + View(in.lagrange_last) * View(in.z_perm_scalar_shift) * scaling_factor; + } + + // Subrelation 2: z_perm initialization + { + using Accumulator = std::tuple_element_t; + using View = typename Accumulator::View; + std::get(accumulator) += View(in.lagrange_first) * View(in.z_perm_scalar) * scaling_factor; + } +} + +// ============================================================================================ +// Grand Product #3: MSM output tuples — (pc, P.x, P.y, msm_size) +// Numerator: MSM table; Denominator: Transcript table +// ============================================================================================ + +template +template +Accumulator ECCVMSetMsmRelationImpl::compute_grand_product_numerator(const AllEntities& in, + const Parameters& params) { - using Accumulator = typename std::tuple_element_t<0, ContainerOverSubrelations>; using View = typename Accumulator::View; - using ShortView = typename std::tuple_element_t<1, ContainerOverSubrelations>::View; + using Constants = ECCVMSetRelationConstants; - // degree-11 - Accumulator numerator_evaluation = compute_grand_product_numerator(in, params); + const auto& beta = params.beta; + const auto& beta_sqr = params.beta_sqr; + const auto& beta_cube = params.beta_cube; + const auto& beta_quartic = params.beta_quartic; + const auto& gamma = params.gamma; - // degree-20 - Accumulator denominator_evaluation = compute_grand_product_denominator(in, params); + const auto third_term_tag = beta_quartic * Constants::THIRD_TERM_TAG; const auto& lagrange_first = View(in.lagrange_first); - const auto& lagrange_last = View(in.lagrange_last); - const auto& lagrange_last_short = ShortView(in.lagrange_last); - - const auto& z_perm = View(in.z_perm); - const auto& z_perm_shift = View(in.z_perm_shift); - const auto& z_perm_shift_short = ShortView(in.z_perm_shift); - - // degree-21 - std::get(accumulator) += - ((z_perm + lagrange_first) * numerator_evaluation - (z_perm_shift + lagrange_last) * denominator_evaluation) * - scaling_factor; - - // Contribution (2) - std::get(accumulator) += lagrange_last_short * z_perm_shift_short * scaling_factor; - - // Contribution (3): Enforce z_perm starts at 0. The grand product initialization relies on - // z_perm[0] = 0 so that (z_perm + lagrange_first) evaluates to 1 at the first row. - using InitAccumulator = std::tuple_element_t; - using InitView = typename InitAccumulator::View; - const auto& lagrange_first_init = InitView(in.lagrange_first); - const auto& z_perm_init = InitView(in.z_perm); - std::get(accumulator) += lagrange_first_init * z_perm_init * scaling_factor; + const auto& partial_msm_transition_shift = View(in.msm_transition_shift); + const auto msm_transition_shift = (-lagrange_first + 1) * partial_msm_transition_shift; + const auto& msm_pc_shift = View(in.msm_pc_shift); + + const auto& msm_x_shift = View(in.msm_accumulator_x_shift); + const auto& msm_y_shift = View(in.msm_accumulator_y_shift); + const auto& msm_size = View(in.msm_size_of_msm); + + auto msm_result_write = + msm_pc_shift + msm_x_shift * beta + msm_y_shift * beta_sqr + msm_size * beta_cube + third_term_tag; + + msm_result_write = msm_transition_shift * (msm_result_write + gamma) + (-msm_transition_shift + 1); + return msm_result_write; // degree-3 } + +template +template +Accumulator ECCVMSetMsmRelationImpl::compute_grand_product_denominator(const AllEntities& in, + const Parameters& params) +{ + using View = typename Accumulator::View; + using Constants = ECCVMSetRelationConstants; + + const auto& gamma = params.gamma; + const auto& beta = params.beta; + const auto& beta_sqr = params.beta_sqr; + const auto& beta_cube = params.beta_cube; + const auto& beta_quartic = params.beta_quartic; + + const auto third_term_tag = beta_quartic * Constants::THIRD_TERM_TAG; + + const auto& transcript_pc_shift = View(in.transcript_pc_shift); + const auto& transcript_msm_x = View(in.transcript_msm_x); + const auto& transcript_msm_y = View(in.transcript_msm_y); + const auto& transcript_msm_transition = View(in.transcript_msm_transition); + const auto& transcript_msm_count = View(in.transcript_msm_count); + const auto& z1_zero = View(in.transcript_z1zero); + const auto& z2_zero = View(in.transcript_z2zero); + const auto& transcript_mul = View(in.transcript_mul); + const auto& base_infinity = View(in.transcript_base_infinity); + + // do not add to count if point at infinity! + auto full_msm_count = + transcript_msm_count + transcript_mul * ((-z1_zero + 1) + (-z2_zero + 1)) * (-base_infinity + 1); + + auto msm_result_read = transcript_pc_shift + transcript_msm_x * beta + transcript_msm_y * beta_sqr + + full_msm_count * beta_cube + third_term_tag; + msm_result_read = transcript_msm_transition * (msm_result_read + gamma) + (-transcript_msm_transition + 1); + return msm_result_read; // degree-4 +} + +template +template +void ECCVMSetMsmRelationImpl::accumulate(ContainerOverSubrelations& accumulator, + const AllEntities& in, + const Parameters& params, + const FF& scaling_factor) +{ + // Subrelation 0: Grand product + { + using Accumulator = std::tuple_element_t; + using View = typename Accumulator::View; + + Accumulator numerator_evaluation = compute_grand_product_numerator(in, params); + Accumulator denominator_evaluation = compute_grand_product_denominator(in, params); + + const auto& lagrange_first = View(in.lagrange_first); + const auto& lagrange_last = View(in.lagrange_last); + const auto& z_perm = View(in.z_perm_msm); + const auto& z_perm_shift = View(in.z_perm_msm_shift); + + std::get(accumulator) += + ((z_perm + lagrange_first) * numerator_evaluation - + (z_perm_shift + lagrange_last) * denominator_evaluation) * + scaling_factor; + } + + // Subrelation 1: Left-shiftable + { + using Accumulator = std::tuple_element_t; + using View = typename Accumulator::View; + std::get(accumulator) += View(in.lagrange_last) * View(in.z_perm_msm_shift) * scaling_factor; + } + + // Subrelation 2: z_perm initialization + { + using Accumulator = std::tuple_element_t; + using View = typename Accumulator::View; + std::get(accumulator) += View(in.lagrange_first) * View(in.z_perm_msm) * scaling_factor; + } +} + } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/stdlib/eccvm_verifier/ecc_relation_consistency.test.cpp b/barretenberg/cpp/src/barretenberg/stdlib/eccvm_verifier/ecc_relation_consistency.test.cpp index 912942f224c7..089b941ce4d3 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/eccvm_verifier/ecc_relation_consistency.test.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/eccvm_verifier/ecc_relation_consistency.test.cpp @@ -50,7 +50,9 @@ TEST_F(EccRelationsConsistency, RecursiveToNativeConsistency) { validate_relation_execution(); - validate_relation_execution(); + validate_relation_execution(); + validate_relation_execution(); + validate_relation_execution(); validate_relation_execution(); validate_relation_execution(); validate_relation_execution(); diff --git a/barretenberg/cpp/src/barretenberg/stdlib/eccvm_verifier/ecc_set_relation.cpp b/barretenberg/cpp/src/barretenberg/stdlib/eccvm_verifier/ecc_set_relation.cpp index 370a9c5ab3f6..3f40a466d228 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/eccvm_verifier/ecc_set_relation.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/eccvm_verifier/ecc_set_relation.cpp @@ -10,7 +10,13 @@ #include "barretenberg/stdlib/primitives/bigfield/bigfield.hpp" namespace bb { -template class ECCVMSetRelationImpl>; -DEFINE_SUMCHECK_VERIFIER_RELATION_CLASS(ECCVMSetRelationImpl, ECCVMRecursiveFlavor); -DEFINE_SUMCHECK_VERIFIER_PERMUTATION_CLASS(ECCVMSetRelationImpl, ECCVMRecursiveFlavor); +template class ECCVMSetWnafRelationImpl>; +template class ECCVMSetScalarRelationImpl>; +template class ECCVMSetMsmRelationImpl>; +DEFINE_SUMCHECK_VERIFIER_RELATION_CLASS(ECCVMSetWnafRelationImpl, ECCVMRecursiveFlavor); +DEFINE_SUMCHECK_VERIFIER_RELATION_CLASS(ECCVMSetScalarRelationImpl, ECCVMRecursiveFlavor); +DEFINE_SUMCHECK_VERIFIER_RELATION_CLASS(ECCVMSetMsmRelationImpl, ECCVMRecursiveFlavor); +DEFINE_SUMCHECK_VERIFIER_PERMUTATION_CLASS(ECCVMSetWnafRelationImpl, ECCVMRecursiveFlavor); +DEFINE_SUMCHECK_VERIFIER_PERMUTATION_CLASS(ECCVMSetScalarRelationImpl, ECCVMRecursiveFlavor); +DEFINE_SUMCHECK_VERIFIER_PERMUTATION_CLASS(ECCVMSetMsmRelationImpl, ECCVMRecursiveFlavor); } // namespace bb