diff --git a/barretenberg/cpp/src/barretenberg/benchmark/relations_bench/relations.bench.cpp b/barretenberg/cpp/src/barretenberg/benchmark/relations_bench/relations.bench.cpp index 3c7291591ce0..1a2acbf93f2e 100644 --- a/barretenberg/cpp/src/barretenberg/benchmark/relations_bench/relations.bench.cpp +++ b/barretenberg/cpp/src/barretenberg/benchmark/relations_bench/relations.bench.cpp @@ -49,7 +49,7 @@ template void execute_relation_for_univaria } // Ultra relations (Sumcheck prover work) -BENCHMARK(execute_relation_for_univariates>); +BENCHMARK(execute_relation_for_univariates>); BENCHMARK(execute_relation_for_univariates>); BENCHMARK(execute_relation_for_univariates>); BENCHMARK(execute_relation_for_univariates>); @@ -64,7 +64,7 @@ BENCHMARK(execute_relation_for_univariates>); // Ultra relations (verifier work) -BENCHMARK(execute_relation_for_values>); +BENCHMARK(execute_relation_for_values>); BENCHMARK(execute_relation_for_values>); BENCHMARK(execute_relation_for_values>); BENCHMARK(execute_relation_for_values>); diff --git a/barretenberg/cpp/src/barretenberg/circuit_checker/ultra_circuit_checker.hpp b/barretenberg/cpp/src/barretenberg/circuit_checker/ultra_circuit_checker.hpp index c67b1dc00fe8..2546b3efb968 100644 --- a/barretenberg/cpp/src/barretenberg/circuit_checker/ultra_circuit_checker.hpp +++ b/barretenberg/cpp/src/barretenberg/circuit_checker/ultra_circuit_checker.hpp @@ -18,7 +18,7 @@ namespace bb { class UltraCircuitChecker { public: using FF = bb::fr; - using Arithmetic = UltraArithmeticRelation; + using Arithmetic = ArithmeticRelation; using Elliptic = EllipticRelation; using Memory = MemoryRelation; using NonNativeField = NonNativeFieldRelation; diff --git a/barretenberg/cpp/src/barretenberg/flavor/flavor.hpp b/barretenberg/cpp/src/barretenberg/flavor/flavor.hpp index 780073b5e556..1fd90d296385 100644 --- a/barretenberg/cpp/src/barretenberg/flavor/flavor.hpp +++ b/barretenberg/cpp/src/barretenberg/flavor/flavor.hpp @@ -461,7 +461,7 @@ template constexpr auto create_sumcheck_tuple_of_tuple * * @example if RelationsTuple = UltraFlavor::Relations_, then the tuple returned by the function is a tuple of length 9, * where the first element of the tuple is an array of length 2 (as the first relation in UltraFlavor::Relations_ is the - * UltraArithmeticRelation, which is made up by two subrelations). + * ArithmeticRelation, which is made up by two subrelations). * * @tparam RelationsTuple */ diff --git a/barretenberg/cpp/src/barretenberg/flavor/mega_flavor.hpp b/barretenberg/cpp/src/barretenberg/flavor/mega_flavor.hpp index 53d0a3fcd5c9..0bcd4bd41e2f 100644 --- a/barretenberg/cpp/src/barretenberg/flavor/mega_flavor.hpp +++ b/barretenberg/cpp/src/barretenberg/flavor/mega_flavor.hpp @@ -76,7 +76,7 @@ class MegaFlavor { // define the tuple of Relations that comprise the Sumcheck relation // Note: made generic for use in MegaRecursive. template - using Relations_ = std::tuple, + using Relations_ = std::tuple, bb::UltraPermutationRelation, bb::LogDerivLookupRelation, bb::DeltaRangeConstraintRelation, diff --git a/barretenberg/cpp/src/barretenberg/flavor/ultra_flavor.hpp b/barretenberg/cpp/src/barretenberg/flavor/ultra_flavor.hpp index bed7a1d34fd3..0b94fe01f934 100644 --- a/barretenberg/cpp/src/barretenberg/flavor/ultra_flavor.hpp +++ b/barretenberg/cpp/src/barretenberg/flavor/ultra_flavor.hpp @@ -81,7 +81,7 @@ class UltraFlavor { // List of relations reflecting the Ultra arithmetisation. WARNING: As UltraKeccak flavor inherits from // Ultra flavor any change of ordering in this tuple needs to be reflected in the smart contract, otherwise // relation accumulation will not match. - using Relations_ = std::tuple, + using Relations_ = std::tuple, bb::UltraPermutationRelation, bb::LogDerivLookupRelation, bb::DeltaRangeConstraintRelation, diff --git a/barretenberg/cpp/src/barretenberg/honk/relation_checker.hpp b/barretenberg/cpp/src/barretenberg/honk/relation_checker.hpp index 184cc9873d11..094a51585036 100644 --- a/barretenberg/cpp/src/barretenberg/honk/relation_checker.hpp +++ b/barretenberg/cpp/src/barretenberg/honk/relation_checker.hpp @@ -93,7 +93,7 @@ template <> class RelationChecker : public RelationChecker>(polynomials, params, "UltraArithmetic"); + Base::check>(polynomials, params, "UltraArithmetic"); Base::check>(polynomials, params, "UltraPermutation"); Base::check>(polynomials, params, "DeltaRangeConstraint"); Base::check>(polynomials, params, "Elliptic"); diff --git a/barretenberg/cpp/src/barretenberg/relations/ultra_arithmetic_relation.hpp b/barretenberg/cpp/src/barretenberg/relations/ultra_arithmetic_relation.hpp index 95e1ed14f5c2..88a868eb0741 100644 --- a/barretenberg/cpp/src/barretenberg/relations/ultra_arithmetic_relation.hpp +++ b/barretenberg/cpp/src/barretenberg/relations/ultra_arithmetic_relation.hpp @@ -9,7 +9,7 @@ namespace bb { -template class UltraArithmeticRelationImpl { +template class ArithmeticRelationImpl { public: using FF = FF_; @@ -25,60 +25,45 @@ template class UltraArithmeticRelationImpl { template inline static bool skip(const AllEntities& in) { return in.q_arith.is_zero(); } /** - * @brief Expression for the Ultra Arithmetic gate. - * @details This relation encapsulates several idenitities, toggled by the value of q_arith in [0, 1, 2, 3, ...]. + * @brief Expression for the Ultra (width-4) Arithmetic gate. + * @details This relation contains two subrelations and encapsulates several identities, toggled by the value of + * q_arith in [0, 1, 2, 3]. * - * The whole formula is: + * Subrelation 1: + * q_arith * + * [ (-1/2) * (q_arith - 3) * (q_m * w_1 * w_2) + \sum_{i=1..4} q_i * w_i + q_c + (q_arith - 1) * w_4_shift] * - * 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 + * Subrelation 2: + * q_arith * (q_arith - 1) * (q_arith - 2) * (w_1 + w_4 - w_1_shift + q_m) * - * This formula results in several cases depending on q_arith: - * 1. q_arith == 0: Arithmetic gate is completely disabled + * These formulas result in several cases depending on q_arith: * - * 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 + * CASE q_arith == 0: Arithmetic gate is completely disabled * - * 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 + * CASE q_arith == 1: Conventional 4-wire Ultra arithmetic relation + * Subrelation 1: q_m * w_1 * w_2 + \sum_{i=1..4} q_i * w_i + q_c + * Subrelation 2: Disabled * - * 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: + * CASE q_arith == 2: Same as above but with an additional linear term: +w_4_shift + * Subrelation 1: q_m * w_1 * w_2 + [ \sum_{i=1..4} q_i * w_i + q_c + w_4_shift ] * 2 + * Subrelation 2: Disabled + * Note: Factor of 2 on the linear term must be accounted for when constructing inputs to the relation. * - * 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 relation is - * defined as C(in(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 * - * (q_arith - 2) * (q_arith - 1) * (w_l + w_4 - w_l_shift + q_m) + * CASE q_arith == 3: + * Subrelation 1: [ \sum_{i=1..4} q_i * w_i + q_c + (2 * w_4_shift) ] * 3 + * Subrelation 2: [ w_1 + w_4 - w_1_shift + q_m ] * 6 + * Note: We are repurposing q_m here as an additive term in the second subrelation. + * Note: Factor of 2 on the w_4_shift term must be accounted for when constructing inputs to the relation. * * @param evals transformed to `evals + C(in(X)...)*scaling_factor` - * @param in an std::array containing the fully extended Univariate edges. - * @param parameters contains beta, gamma, and public_input_delta, .... + * @param in Inputs to the relation algebra + * @param parameters Unused in this relation * @param scaling_factor optional term to scale the evaluation before adding to evals. */ template inline static void accumulate(ContainerOverSubrelations& evals, const AllEntities& in, - const Parameters&, + BB_UNUSED const Parameters& params, const FF& scaling_factor) { using Accumulator = std::tuple_element_t<0, ContainerOverSubrelations>; @@ -91,6 +76,7 @@ template class UltraArithmeticRelationImpl { auto q_arith_sub_1 = q_arith_m - FF(1); auto scaled_q_arith = q_arith_m * scaling_factor; + // Subrelation 1 { using Accumulator = std::tuple_element_t<0, ContainerOverSubrelations>; @@ -111,6 +97,7 @@ template class UltraArithmeticRelationImpl { std::get<0>(evals) += (tmp0 + Accumulator(tmp1)) * Accumulator(scaled_q_arith); } + // Subrelation 2 { using ShortAccumulator = std::tuple_element_t<1, ContainerOverSubrelations>; @@ -124,5 +111,5 @@ template class UltraArithmeticRelationImpl { }; }; -template using UltraArithmeticRelation = Relation>; +template using ArithmeticRelation = Relation>; } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/relations/ultra_relation_consistency.test.cpp b/barretenberg/cpp/src/barretenberg/relations/ultra_relation_consistency.test.cpp index c96fea4e30c7..3fc636c2e254 100644 --- a/barretenberg/cpp/src/barretenberg/relations/ultra_relation_consistency.test.cpp +++ b/barretenberg/cpp/src/barretenberg/relations/ultra_relation_consistency.test.cpp @@ -108,13 +108,13 @@ class UltraRelationConsistency : public testing::Test { }; }; -TEST_F(UltraRelationConsistency, UltraArithmeticRelation) +TEST_F(UltraRelationConsistency, ArithmeticRelation) { - const auto run_test = [](bool random_inputs) { - using Relation = UltraArithmeticRelation; + const auto run_test = [](bool random_inputs, const FF& q_arith_value = FF::random_element()) { + using Relation = ArithmeticRelation; using SumcheckArrayOfValuesOverSubrelations = typename Relation::SumcheckArrayOfValuesOverSubrelations; - const InputElements input_elements = random_inputs ? InputElements::get_random() : InputElements::get_special(); + InputElements input_elements = random_inputs ? InputElements::get_random() : InputElements::get_special(); const auto& w_1 = input_elements.w_l; const auto& w_1_shift = input_elements.w_l_shift; const auto& w_2 = input_elements.w_r; @@ -127,21 +127,48 @@ TEST_F(UltraRelationConsistency, UltraArithmeticRelation) const auto& q_o = input_elements.q_o; const auto& q_4 = input_elements.q_4; const auto& q_c = input_elements.q_c; + + // Set specific q_arith value to enable testing different modes of the arithmetic relation + input_elements.q_arith = q_arith_value; const auto& q_arith = input_elements.q_arith; SumcheckArrayOfValuesOverSubrelations expected_values; static const FF neg_half = FF(-2).invert(); - // 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_values[0] = contribution_1; + FF contribution_1 = FF(0); + FF contribution_2 = FF(0); + if (q_arith == FF(1)) { + // Contribution 1 + contribution_1 = (q_m * w_2 * w_1) + (q_l * w_1) + (q_r * w_2) + (q_o * w_3) + (q_4 * w_4) + q_c; + + // Contribution 2: None + } else if (q_arith == FF(2)) { + // Contribution 1 + contribution_1 = (q_m * w_2 * w_1); + contribution_1 += ((q_l * w_1) + (q_r * w_2) + (q_o * w_3) + (q_4 * w_4) + w_4_shift + q_c) * FF(2); + + // Contribution 2: None + } else if (q_arith == FF(3)) { + // Contribution 1 + contribution_1 = (q_l * w_1) + (q_r * w_2) + (q_o * w_3) + (q_4 * w_4) + q_c; + contribution_1 += w_4_shift * FF(2); + contribution_1 *= FF(3); + + // Contribution 2 + contribution_2 = (w_1 + w_4 - w_1_shift + q_m) * FF(6); + } else { + // Contribution 1 + 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; + + // Contribution 2 + contribution_2 = (w_1 + w_4 - w_1_shift + q_m); + contribution_2 *= (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_values[0] = contribution_1; expected_values[1] = contribution_2; const auto parameters = RelationParameters::get_random(); @@ -150,6 +177,9 @@ TEST_F(UltraRelationConsistency, UltraArithmeticRelation) }; run_test(/*random_inputs=*/false); run_test(/*random_inputs=*/true); + run_test(/*random_inputs=*/true, /*q_arith_value=*/FF(1)); + run_test(/*random_inputs=*/true, /*q_arith_value=*/FF(2)); + run_test(/*random_inputs=*/true, /*q_arith_value=*/FF(3)); }; TEST_F(UltraRelationConsistency, UltraPermutationRelation) diff --git a/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/ultra_circuit_builder.cpp b/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/ultra_circuit_builder.cpp index 8fcaba950327..3f52eaccfe05 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/ultra_circuit_builder.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/ultra_circuit_builder.cpp @@ -251,7 +251,11 @@ void UltraCircuitBuilder_::create_big_mul_add_gate(const mul_qua { this->assert_valid_variables({ in.a, in.b, in.c, in.d }); blocks.arithmetic.populate_wires(in.a, in.b, in.c, in.d); - blocks.arithmetic.q_m().emplace_back(include_next_gate_w_4 ? in.mul_scaling * FF(2) : in.mul_scaling); + // If include_next_gate_w_4 is true then we set q_arith = 2. In this case, the linear term in the ArithmeticRelation + // is scaled by a factor of 2. We compensate here by scaling the quadratic term by 2 to achieve the constraint: + // 2 * [q_m * w_1 * w_2 + \sum_{i=1..4} q_i * w_i + q_c + w_4_shift] = 0 + const FF mul_scaling = include_next_gate_w_4 ? in.mul_scaling * FF(2) : in.mul_scaling; + blocks.arithmetic.q_m().emplace_back(mul_scaling); blocks.arithmetic.q_1().emplace_back(in.a_scaling); blocks.arithmetic.q_2().emplace_back(in.b_scaling); blocks.arithmetic.q_3().emplace_back(in.c_scaling); @@ -1661,13 +1665,16 @@ std::array UltraCircuitBuilder_::evaluate_non_nativ block.populate_wires(x_2, y_2, z_2, z_1); block.populate_wires(x_3, y_3, z_3, this->zero_idx()); + // When q_arith == 3, w_4_shift is scaled by 2 (see ArithmeticRelation for details). Therefore, for consistency we + // also scale each linear term by this factor of 2 so that the constraint is effectively: + // (q_l * w_1) + (q_r * w_2) + (q_o * w_3) + (q_4 * w_4) + q_c + w_4_shift = 0 + const FF linear_term_scale_factor = 2; block.q_m().emplace_back(addconstp); block.q_1().emplace_back(0); - block.q_2().emplace_back(-x_mulconst0 * - 2); // scale constants by 2. If q_arith = 3 then w_4_omega value (z0) gets scaled by 2x - block.q_3().emplace_back(-y_mulconst0 * 2); // z_0 - (x_0 * -xmulconst0) - (y_0 * ymulconst0) = 0 => z_0 = x_0 + y_0 + block.q_2().emplace_back(-x_mulconst0 * linear_term_scale_factor); + block.q_3().emplace_back(-y_mulconst0 * linear_term_scale_factor); block.q_4().emplace_back(0); - block.q_c().emplace_back(-addconst0 * 2); + block.q_c().emplace_back(-addconst0 * linear_term_scale_factor); block.set_gate_selector(3); block.q_m().emplace_back(0); @@ -1773,12 +1780,16 @@ std::array UltraCircuitBuilder_::evaluate_non_nativ block.populate_wires(x_2, y_2, z_2, z_1); block.populate_wires(x_3, y_3, z_3, this->zero_idx()); + // When q_arith == 3, w_4_shift is scaled by 2 (see ArithmeticRelation for details). Therefore, for consistency we + // also scale each linear term by this factor of 2 so that the constraint is effectively: + // (q_l * w_1) + (q_r * w_2) + (q_o * w_3) + (q_4 * w_4) + q_c + w_4_shift = 0 + const FF linear_term_scale_factor = 2; block.q_m().emplace_back(-addconstp); block.q_1().emplace_back(0); - block.q_2().emplace_back(-x_mulconst0 * 2); - block.q_3().emplace_back(y_mulconst0 * 2); // z_0 + (x_0 * -xmulconst0) + (y_0 * ymulconst0) = 0 => z_0 = x_0 - y_0 + block.q_2().emplace_back(-x_mulconst0 * linear_term_scale_factor); + block.q_3().emplace_back(y_mulconst0 * linear_term_scale_factor); block.q_4().emplace_back(0); - block.q_c().emplace_back(-addconst0 * 2); + block.q_c().emplace_back(-addconst0 * linear_term_scale_factor); block.set_gate_selector(3); block.q_m().emplace_back(0);