diff --git a/barretenberg/cpp/bb-civc-inputs.tar.gz b/barretenberg/cpp/bb-civc-inputs.tar.gz deleted file mode 100644 index 9347dcd9a0cd..000000000000 Binary files a/barretenberg/cpp/bb-civc-inputs.tar.gz and /dev/null differ diff --git a/barretenberg/cpp/scripts/test_civc_standalone_vks_havent_changed.sh b/barretenberg/cpp/scripts/test_civc_standalone_vks_havent_changed.sh index 63218675ebc9..99500dcc7bac 100755 --- a/barretenberg/cpp/scripts/test_civc_standalone_vks_havent_changed.sh +++ b/barretenberg/cpp/scripts/test_civc_standalone_vks_havent_changed.sh @@ -11,7 +11,7 @@ cd .. # - Generate a hash for versioning: sha256sum bb-civc-inputs.tar.gz # - Upload the compressed results: aws s3 cp bb-civc-inputs.tar.gz s3://aztec-ci-artifacts/protocol/bb-civc-inputs-[hash(0:8)].tar.gz # Note: In case of the "Test suite failed to run ... Unexpected token 'with' " error, need to run: docker pull aztecprotocol/build:3.0 -pinned_civc_inputs_url="https://aztec-ci-artifacts.s3.us-east-2.amazonaws.com/protocol/bb-civc-inputs-8b19513e.tar.gz" +pinned_civc_inputs_url="https://aztec-ci-artifacts.s3.us-east-2.amazonaws.com/protocol/bb-civc-inputs-95b3f0bb.tar.gz" # For easily rerunning the inputs generation if [[ "${1:-}" == "--update-inputs" ]]; then diff --git a/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description_ultra_recursive_verifier.test.cpp b/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description_ultra_recursive_verifier.test.cpp index fc1115351a94..45031d0c2ec9 100644 --- a/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description_ultra_recursive_verifier.test.cpp +++ b/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description_ultra_recursive_verifier.test.cpp @@ -130,7 +130,7 @@ template class BoomerangRecursiveVerifierTest : publi outer_circuit.finalize_circuit(false); auto graph = cdg::StaticAnalyzer(outer_circuit); auto connected_components = graph.find_connected_components(); - EXPECT_EQ(connected_components.size(), 4); + EXPECT_EQ(connected_components.size(), 3); info("Connected components: ", connected_components.size()); auto variables_in_one_gate = graph.show_variables_in_one_gate(outer_circuit); EXPECT_EQ(variables_in_one_gate.size(), 2); diff --git a/barretenberg/cpp/src/barretenberg/common/log.hpp b/barretenberg/cpp/src/barretenberg/common/log.hpp index a200473c6dc2..9cc024193ff7 100644 --- a/barretenberg/cpp/src/barretenberg/common/log.hpp +++ b/barretenberg/cpp/src/barretenberg/common/log.hpp @@ -10,6 +10,13 @@ #define BENCHMARK_INFO_SEPARATOR "#" #define BENCHMARK_INFO_SUFFIX "##BENCHMARK_INFO_SUFFIX##" +#define BENCH_GATE_COUNT_START(builder, op_name) uint64_t __bench_before = builder.get_estimated_num_finalized_gates(); + +#define BENCH_GATE_COUNT_END(builder, op_name) \ + uint64_t __bench_after = builder.get_estimated_num_finalized_gates(); \ + std::cerr << "num gates with " << op_name << " = " << __bench_after - __bench_before << std::endl; \ + benchmark_info(Builder::NAME_STRING, "Bigfield", op_name, "Gate Count", __bench_after - __bench_before); + template std::string format(Args... args) { std::ostringstream os; diff --git a/barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/ultra_recursive_verifier.test.cpp b/barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/ultra_recursive_verifier.test.cpp index 89307b4fac4c..5410d22ea203 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/ultra_recursive_verifier.test.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/ultra_recursive_verifier.test.cpp @@ -263,7 +263,7 @@ template class RecursiveVerifierTest : public testing } // Check the size of the recursive verifier if constexpr (std::same_as>) { - uint32_t NUM_GATES_EXPECTED = 875529; + uint32_t NUM_GATES_EXPECTED = 870572; BB_ASSERT_EQ(static_cast(outer_circuit.get_num_finalized_gates()), NUM_GATES_EXPECTED, "MegaZKHonk Recursive verifier changed in Ultra gate count! Update this value if you " diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield.hpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield.hpp index 736317c9ee14..ed2f6d0b8ef4 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield.hpp @@ -42,7 +42,7 @@ template class bigfield { * */ struct Limb { - Limb() {} + Limb() = default; Limb(const field_t& input, const uint256_t& max = DEFAULT_MAXIMUM_LIMB) : element(input) { @@ -59,9 +59,10 @@ template class bigfield { return os; } Limb(const Limb& other) = default; - Limb(Limb&& other) = default; + Limb(Limb&& other) noexcept = default; Limb& operator=(const Limb& other) = default; - Limb& operator=(Limb&& other) = default; + Limb& operator=(Limb&& other) noexcept = default; + ~Limb() = default; field_t element; uint256_t maximum_value; @@ -263,7 +264,10 @@ template class bigfield { bigfield(const bigfield& other); // Move constructor - bigfield(bigfield&& other); + bigfield(bigfield&& other) noexcept; + + // Destructor + ~bigfield() = default; /** * @brief Creates a bigfield element from a uint512_t. @@ -294,15 +298,18 @@ template class bigfield { } bigfield& operator=(const bigfield& other); - bigfield& operator=(bigfield&& other); - // code assumes modulus is at most 256 bits so good to define it via a uint256_t + bigfield& operator=(bigfield&& other) noexcept; + + // Code assumes modulus is at most 256 bits so good to define it via a uint256_t static constexpr uint256_t modulus = (uint256_t(T::modulus_0, T::modulus_1, T::modulus_2, T::modulus_3)); - static constexpr uint512_t modulus_u512 = uint512_t(modulus); + static constexpr uint512_t modulus_u512 = static_cast(modulus); static constexpr uint64_t NUM_LIMB_BITS = NUM_LIMB_BITS_IN_FIELD_SIMULATION; static constexpr uint64_t NUM_LAST_LIMB_BITS = modulus_u512.get_msb() + 1 - (NUM_LIMB_BITS * 3); + // The quotient reduction checks currently only support >=250 bit moduli and moduli >256 have never been tested // (Check zkSecurity audit report issue #12 for explanation) static_assert(modulus_u512.get_msb() + 1 >= 250 && modulus_u512.get_msb() + 1 <= 256); + inline static const uint1024_t DEFAULT_MAXIMUM_REMAINDER = (uint1024_t(1) << (NUM_LIMB_BITS * 3 + NUM_LAST_LIMB_BITS)) - uint1024_t(1); static constexpr uint256_t DEFAULT_MAXIMUM_LIMB = (uint256_t(1) << NUM_LIMB_BITS) - uint256_t(1); @@ -327,13 +334,13 @@ template class bigfield { static constexpr bb::fr shift_right_2 = bb::fr(1) / shift_2; static constexpr bb::fr negative_prime_modulus_mod_binary_basis = -bb::fr(uint256_t(modulus_u512)); static constexpr uint512_t negative_prime_modulus = binary_basis.modulus - target_basis.modulus; - static constexpr uint256_t neg_modulus_limbs_u256[NUM_LIMBS]{ + static constexpr std::array neg_modulus_limbs_u256{ negative_prime_modulus.slice(0, NUM_LIMB_BITS).lo, negative_prime_modulus.slice(NUM_LIMB_BITS, NUM_LIMB_BITS * 2).lo, negative_prime_modulus.slice(NUM_LIMB_BITS * 2, NUM_LIMB_BITS * 3).lo, negative_prime_modulus.slice(NUM_LIMB_BITS * 3, NUM_LIMB_BITS * 4).lo, }; - static constexpr bb::fr neg_modulus_limbs[NUM_LIMBS]{ + static constexpr std::array neg_modulus_limbs{ bb::fr(negative_prime_modulus.slice(0, NUM_LIMB_BITS).lo), bb::fr(negative_prime_modulus.slice(NUM_LIMB_BITS, NUM_LIMB_BITS * 2).lo), bb::fr(negative_prime_modulus.slice(NUM_LIMB_BITS * 2, NUM_LIMB_BITS * 3).lo), @@ -391,7 +398,7 @@ template class bigfield { * @param other_maximum_value The maximum value of other * @return bigfield Result */ - bigfield add_to_lower_limb(const field_t& other, uint256_t other_maximum_value) const; + bigfield add_to_lower_limb(const field_t& other, const uint256_t& other_maximum_value) const; /** * @brief Adds two bigfield elements. Inputs are reduced to the modulus if necessary. Requires 4 gates if both @@ -569,6 +576,16 @@ template class bigfield { static bigfield div_check_denominator_nonzero(const std::vector& numerators, const bigfield& denominator); bigfield conditional_negate(const bool_t& predicate) const; + + /** + * @brief Create an element which is equal to either this or other based on the predicate + * + * @tparam Builder + * @tparam T + * @param other The other bigfield element + * @param predicate Predicate controlling the result (0 for this, 1 for the other) + * @return Resulting element + */ bigfield conditional_select(const bigfield& other, const bool_t& predicate) const; static bigfield conditional_assign(const bool_t& predicate, const bigfield& lhs, const bigfield& rhs) { @@ -578,7 +595,7 @@ template class bigfield { bool_t operator==(const bigfield& other) const; void assert_is_in_field() const; - void assert_less_than(const uint256_t upper_limit) const; + void assert_less_than(const uint256_t& upper_limit) const; void assert_equal(const bigfield& other) const; void assert_is_not_equal(const bigfield& other) const; @@ -876,13 +893,28 @@ template class bigfield { // The rationale of the expression is we should not overflow Fr when applying any bigfield operation (e.g. *) and // starting with this max limb size // - // In multiplication of bigfield elements a * b, we encounter sum of limbs multiplications of form: 2^L . ai . bj. - // Suppose we are adding 2^k such terms. Let Q be the max bitsize of a limb. We want to ensure that the sum + // In multiplication of bigfield elements a * b, we encounter sum of limbs multiplications of form: + // c0 := a0 * b0 + // c1 := a1 * b0 + a0 * b1 + // c2 := a2 * b0 + a1 * b1 + a0 * b2 + // c3 := a3 * b0 + a2 * b1 + a1 * b2 + a0 * b3 + // output: + // lo := c0 + c1 * 2^L, + // hi := c2 + c3 * 2^L. + // Since hi term contains upto 4 limb-products, we must ensure that the hi term does not overflow the native field + // modulus. Suppose we are adding 2^k such terms. Let Q be the max bitsize of a limb. We want to ensure that the sum // doesn't overflow the native field modulus. Hence: - // 2^k . 2^L . 2^Q . 2^Q < n ==> Q < (log(n) - k - L) / 2 + // max(∑ hi) = max(∑ c2 + c3 * 2^L) + // = max(∑ c2) + max(∑ c3 * 2^L) + // = 2^k * (3 * 2^2Q) + 2^k * 2^L * (4 * 2^2Q) + // < 2^k * (2^L + 1) * (4 * 2^2Q) + // < n + // ==> 2^k * 2^L * 2^(2Q + 2) < n + // ==> 2Q + 2 < (log(n) - k - L) + // ==> Q < ((log(n) - k - L) - 2) / 2 // static constexpr uint64_t MAXIMUM_LIMB_SIZE_THAT_WOULDNT_OVERFLOW = - (bb::fr::modulus.get_msb() - MAX_ADDITION_LOG - NUM_LIMB_BITS) / 2; + (bb::fr::modulus.get_msb() - MAX_ADDITION_LOG - NUM_LIMB_BITS - 2) / 2; // If the logarithm of the maximum value of a limb is more than this, we need to reduce. // We allow an element to be added to itself 10 times, so we allow the limb to grow by 10 bits. @@ -974,11 +1006,11 @@ template class bigfield { * * @warning THIS FUNCTION IS UNSAFE TO USE IN CIRCUITS AS IT DOES NOT PROTECT AGAINST CRT OVERFLOWS. */ - static void unsafe_evaluate_multiply_add(const bigfield& left, - const bigfield& right_mul, + static void unsafe_evaluate_multiply_add(const bigfield& input_left, + const bigfield& input_to_mul, const std::vector& to_add, - const bigfield& quotient, - const std::vector& remainders); + const bigfield& input_quotient, + const std::vector& input_remainders); /** * @brief Evaluate a relation involving multiple multiplications and additions. @@ -1054,6 +1086,35 @@ template class bigfield { */ void sanity_check() const; + /** + * @brief Get the maximum values of the binary basis limbs. + * + * @return std::array, NUM_LIMBS> An array containing the maximum values of the binary basis limbs. + */ + std::array get_binary_basis_limb_maximums() + { + std::array limb_maximums; + for (size_t i = 0; i < NUM_LIMBS; i++) { + limb_maximums[i] = binary_basis_limbs[i].maximum_value; + } + return limb_maximums; + } + + /** + * @brief Compute the partial multiplication of two uint256_t arrays using schoolbook multiplication. + * + * @param a_limbs + * @param b_limbs + * @return std::pair + * + * @details Regular schoolbook multiplication of two arrays each with L = 4 limbs will produce a result of size + * 2 * L - 1 = 7. In this context, we can ignore the last three limbs as those terms have multiplicands: (2^4L, + * 2^5L, 2^6L) and since we are working modulo 2^t = 2^4L, those terms will always be zero. This is why we call this + * helper function "partial schoolbook multiplication". + */ + static std::pair compute_partial_schoolbook_multiplication( + const std::array& a_limbs, const std::array& b_limbs); + }; // namespace stdlib template inline std::ostream& operator<<(std::ostream& os, bigfield const& v) diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield.test.cpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield.test.cpp index 9cf7ccc6f157..b90b1cc7f17d 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield.test.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield.test.cpp @@ -18,14 +18,6 @@ using namespace bb; -/* A note regarding Plookup: - stdlib_bigfield_plookup tests were present when this file was standardized - to be more proving system-agnostic. Those tests are commented out below, but modified - in the following ways: - - pbigfield_t was replaced by bn254::BaseField; - - pwitness_t was replaced by bn254::witness_ct. -*/ - namespace { auto& engine = numeric::get_debug_randomness(); } @@ -33,12 +25,13 @@ auto& engine = numeric::get_debug_randomness(); STANDARD_TESTING_TAGS template class stdlib_bigfield : public testing::Test { - typedef stdlib::bn254 bn254; + using bn254 = stdlib::bn254; - typedef typename bn254::ScalarField fr_ct; - typedef typename bn254::BaseField fq_ct; - typedef typename bn254::public_witness_ct public_witness_ct; - typedef typename bn254::witness_ct witness_ct; + using fr_ct = typename bn254::ScalarField; + using fq_ct = typename bn254::BaseField; + using public_witness_ct = typename bn254::public_witness_ct; + using witness_ct = typename bn254::witness_ct; + using bool_ct = typename bn254::bool_ct; public: static void test_add_to_lower_limb_regression() @@ -70,6 +63,7 @@ template class stdlib_bigfield : public testing::Test { bool result = CircuitChecker::check(builder); EXPECT_EQ(result, true); } + // The bug happens when we are applying the CRT formula to a*b < r, which can happen when using the division // operator static void test_division_formula_bug() @@ -96,77 +90,109 @@ template class stdlib_bigfield : public testing::Test { EXPECT_EQ(result, true); } - static std::pair get_random_element(Builder* ctx) + // Gets a random bigfield element that is a circuit-witness + static std::pair get_random_witness(Builder* builder, bool reduce_input = false) { - fq elt = fq::random_element(); - return std::make_pair(elt, fq_ct::from_witness(ctx, elt)); + fq elt_native = fq::random_element(); + if (reduce_input) { + elt_native = elt_native.reduce_once().reduce_once(); + } + fr elt_native_lo = fr(uint256_t(elt_native).slice(0, fq_ct::NUM_LIMB_BITS * 2)); + fr elt_native_hi = fr(uint256_t(elt_native).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)); + fq_ct elt_ct(witness_ct(builder, elt_native_lo), witness_ct(builder, elt_native_hi)); + return std::make_pair(elt_native, elt_ct); + } + + // Gets a random bigfield element that is a circuit-constant + static std::pair get_random_constant(Builder* builder, bool reduce_input = false) + { + fq elt_native = fq::random_element(); + if (reduce_input) { + elt_native = elt_native.reduce_once().reduce_once(); + } + fq_ct elt_ct(builder, uint256_t(elt_native)); + return std::make_pair(elt_native, elt_ct); } - static std::pair, std::vector> get_random_elements(Builder* ctx, size_t num) + static std::pair, std::vector> get_random_witnesses(Builder* builder, + size_t num, + bool reduce_input = false) { std::vector elts(num); std::vector big_elts(num); for (size_t i = 0; i < num; ++i) { - auto [elt, big_elt] = get_random_element(ctx); + auto [elt, big_elt] = get_random_witness(builder, reduce_input); + elts[i] = elt; + big_elts[i] = big_elt; + } + return std::make_pair(elts, big_elts); + } + + static std::pair, std::vector> get_random_constants(Builder* builder, + size_t num, + bool reduce_input = false) + { + std::vector elts(num); + std::vector big_elts(num); + for (size_t i = 0; i < num; ++i) { + auto [elt, big_elt] = get_random_constant(builder, reduce_input); elts[i] = elt; big_elts[i] = big_elt; } return std::make_pair(elts, big_elts); } - public: static void test_basic_tag_logic() { auto builder = Builder(); - auto input = fq::random_element(); - fq_ct a(witness_ct(&builder, fr(uint256_t(input).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, fr(uint256_t(input).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - a.binary_basis_limbs[0].element.set_origin_tag(submitted_value_origin_tag); - a.binary_basis_limbs[1].element.set_origin_tag(challenge_origin_tag); - a.prime_basis_limb.set_origin_tag(next_challenge_tag); + auto [a_native, a_ct] = get_random_witness(&builder); // fq, fq_ct + + a_ct.binary_basis_limbs[0].element.set_origin_tag(submitted_value_origin_tag); + a_ct.binary_basis_limbs[1].element.set_origin_tag(challenge_origin_tag); + a_ct.prime_basis_limb.set_origin_tag(next_challenge_tag); - EXPECT_EQ(a.get_origin_tag(), first_second_third_merged_tag); + EXPECT_EQ(a_ct.get_origin_tag(), first_second_third_merged_tag); - a.set_origin_tag(clear_tag); - EXPECT_EQ(a.binary_basis_limbs[0].element.get_origin_tag(), clear_tag); - EXPECT_EQ(a.binary_basis_limbs[1].element.get_origin_tag(), clear_tag); - EXPECT_EQ(a.binary_basis_limbs[2].element.get_origin_tag(), clear_tag); - EXPECT_EQ(a.binary_basis_limbs[3].element.get_origin_tag(), clear_tag); - EXPECT_EQ(a.prime_basis_limb.get_origin_tag(), clear_tag); + a_ct.set_origin_tag(clear_tag); + EXPECT_EQ(a_ct.binary_basis_limbs[0].element.get_origin_tag(), clear_tag); + EXPECT_EQ(a_ct.binary_basis_limbs[1].element.get_origin_tag(), clear_tag); + EXPECT_EQ(a_ct.binary_basis_limbs[2].element.get_origin_tag(), clear_tag); + EXPECT_EQ(a_ct.binary_basis_limbs[3].element.get_origin_tag(), clear_tag); + EXPECT_EQ(a_ct.prime_basis_limb.get_origin_tag(), clear_tag); #ifndef NDEBUG - a.set_origin_tag(instant_death_tag); - EXPECT_THROW(a + a, std::runtime_error); + a_ct.set_origin_tag(instant_death_tag); + EXPECT_THROW(a_ct + a_ct, std::runtime_error); #endif } + static void test_mul() { auto builder = Builder(); size_t num_repetitions = 4; for (size_t i = 0; i < num_repetitions; ++i) { - fq inputs[3]{ fq::random_element(), fq::random_element(), fq::random_element() }; - fq_ct a(witness_ct(&builder, fr(uint256_t(inputs[0]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[0]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - a.set_origin_tag(submitted_value_origin_tag); - fq_ct b(witness_ct(&builder, fr(uint256_t(inputs[1]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[1]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - b.set_origin_tag(challenge_origin_tag); + auto [a_native, a_ct] = get_random_witness(&builder); // fq, fq_ct + auto [b_native, b_ct] = get_random_witness(&builder); // fq, fq_ct + a_ct.set_origin_tag(submitted_value_origin_tag); + b_ct.set_origin_tag(challenge_origin_tag); - uint64_t before = builder.get_estimated_num_finalized_gates(); - fq_ct c = a * b; - EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag); - uint64_t after = builder.get_estimated_num_finalized_gates(); - // Don't profile 1st repetition. It sets up a lookup table, cost is not representative of a typical mul - if (i == num_repetitions - 2) { - std::cerr << "num gates per mul = " << after - before << std::endl; - benchmark_info(Builder::NAME_STRING, "Bigfield", "MUL", "Gate Count", after - before); + fq_ct c_ct; + if (i == num_repetitions - 1) { + // We don't profile in the first repetition. It sets up a lookup table, cost is not representative of a + // typical mul. + BENCH_GATE_COUNT_START(builder, "MUL"); + c_ct = a_ct * b_ct; + BENCH_GATE_COUNT_END(builder, "MUL"); + } else { + c_ct = a_ct * b_ct; } - fq expected = (inputs[0] * inputs[1]); + // Multiplication merges tags + EXPECT_EQ(c_ct.get_origin_tag(), first_two_merged_tag); + + fq expected = (a_native * b_native); expected = expected.from_montgomery_form(); - uint512_t result = c.get_value(); + uint512_t result = c_ct.get_value(); EXPECT_EQ(result.lo.data[0], expected.data[0]); EXPECT_EQ(result.lo.data[1], expected.data[1]); @@ -186,26 +212,24 @@ template class stdlib_bigfield : public testing::Test { auto builder = Builder(); size_t num_repetitions = 10; for (size_t i = 0; i < num_repetitions; ++i) { - fq inputs[3]{ fq::random_element(), fq::random_element(), fq::random_element() }; - fq_ct a(witness_ct(&builder, fr(uint256_t(inputs[0]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[0]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - a.set_origin_tag(next_challenge_tag); + auto [a_native, a_ct] = get_random_witness(&builder); // fq, fq_ct + a_ct.set_origin_tag(next_challenge_tag); - uint64_t before = builder.get_estimated_num_finalized_gates(); - fq_ct c = a.sqr(); - - // Squaring preserves tags - EXPECT_EQ(a.get_origin_tag(), next_challenge_tag); - uint64_t after = builder.get_estimated_num_finalized_gates(); + fq_ct c_ct; if (i == num_repetitions - 1) { - std::cerr << "num gates per mul = " << after - before << std::endl; - benchmark_info(Builder::NAME_STRING, "Bigfield", "SQR", "Gate Count", after - before); + BENCH_GATE_COUNT_START(builder, "SQR"); + c_ct = a_ct.sqr(); + BENCH_GATE_COUNT_END(builder, "SQR"); + } else { + c_ct = a_ct.sqr(); } - fq expected = (inputs[0].sqr()); + // Squaring preserves tags + EXPECT_EQ(a_ct.get_origin_tag(), next_challenge_tag); + + fq expected = (a_native.sqr()); expected = expected.from_montgomery_form(); - uint512_t result = c.get_value(); + uint512_t result = c_ct.get_value(); EXPECT_EQ(result.lo.data[0], expected.data[0]); EXPECT_EQ(result.lo.data[1], expected.data[1]); @@ -225,34 +249,28 @@ template class stdlib_bigfield : public testing::Test { auto builder = Builder(); size_t num_repetitions = 1; for (size_t i = 0; i < num_repetitions; ++i) { - fq inputs[3]{ fq::random_element(), fq::random_element(), fq::random_element() }; - fq_ct a(witness_ct(&builder, fr(uint256_t(inputs[0]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[0]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - fq_ct b(witness_ct(&builder, fr(uint256_t(inputs[1]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[1]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - - fq_ct c(witness_ct(&builder, fr(uint256_t(inputs[2]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[2]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - a.set_origin_tag(challenge_origin_tag); - b.set_origin_tag(submitted_value_origin_tag); - c.set_origin_tag(next_challenge_tag); - uint64_t before = builder.get_estimated_num_finalized_gates(); - fq_ct d = a.madd(b, { c }); - - // Madd merges tags - EXPECT_EQ(d.get_origin_tag(), first_second_third_merged_tag); - uint64_t after = builder.get_estimated_num_finalized_gates(); + auto [a_native, a_ct] = get_random_witness(&builder); // fq, fq_ct + auto [b_native, b_ct] = get_random_witness(&builder); // fq, fq_ct + auto [c_native, c_ct] = get_random_witness(&builder); // fq, fq_ct + a_ct.set_origin_tag(challenge_origin_tag); + b_ct.set_origin_tag(submitted_value_origin_tag); + c_ct.set_origin_tag(next_challenge_tag); + + fq_ct d_ct; if (i == num_repetitions - 1) { - std::cerr << "num gates per mul = " << after - before << std::endl; - benchmark_info(Builder::NAME_STRING, "Bigfield", "MADD", "Gate Count", after - before); + BENCH_GATE_COUNT_START(builder, "MADD"); + d_ct = a_ct.madd(b_ct, { c_ct }); + BENCH_GATE_COUNT_END(builder, "MADD"); + } else { + d_ct = a_ct.madd(b_ct, { c_ct }); } - fq expected = (inputs[0] * inputs[1]) + inputs[2]; + // Madd merges tags + EXPECT_EQ(d_ct.get_origin_tag(), first_second_third_merged_tag); + + fq expected = (a_native * b_native) + c_native; expected = expected.from_montgomery_form(); - uint512_t result = d.get_value(); + uint512_t result = d_ct.get_value(); EXPECT_EQ(result.lo.data[0], expected.data[0]); EXPECT_EQ(result.lo.data[1], expected.data[1]); @@ -273,52 +291,39 @@ template class stdlib_bigfield : public testing::Test { size_t num_repetitions = 1; const size_t number_of_madds = 16; for (size_t i = 0; i < num_repetitions; ++i) { - fq mul_left_values[number_of_madds]; - fq mul_right_values[number_of_madds]; - fq to_add_values[number_of_madds]; + // Get random witnesses for the multiplicands and the to_add values + auto [mul_left_native, mul_left_ct] = + get_random_witnesses(&builder, number_of_madds); // std::vector, std::vector + auto [mul_right_native, mul_right_ct] = + get_random_witnesses(&builder, number_of_madds); // std::vector, std::vector + auto [to_add_native, to_add_ct] = + get_random_witnesses(&builder, number_of_madds); // std::vector, std::vector + + // Set the origin tags of the last multiplicands and summand + mul_left_ct[number_of_madds - 1].set_origin_tag(submitted_value_origin_tag); + mul_right_ct[number_of_madds - 1].set_origin_tag(challenge_origin_tag); + to_add_ct[number_of_madds - 1].set_origin_tag(next_challenge_tag); + + fq_ct f_ct; + if (i == num_repetitions - 1) { + BENCH_GATE_COUNT_START(builder, "MULT_MADD"); + f_ct = fq_ct::mult_madd(mul_left_ct, mul_right_ct, to_add_ct); + BENCH_GATE_COUNT_END(builder, "MULT_MADD"); + } else { + f_ct = fq_ct::mult_madd(mul_left_ct, mul_right_ct, to_add_ct); + } - fq expected(0); + // mult_madd merges tags + EXPECT_EQ(f_ct.get_origin_tag(), first_second_third_merged_tag); - std::vector mul_left; - std::vector mul_right; - std::vector to_add; - mul_left.reserve(number_of_madds); - mul_right.reserve(number_of_madds); - to_add.reserve(number_of_madds); + // Compute expected value + fq expected(0); for (size_t j = 0; j < number_of_madds; j++) { - mul_left_values[j] = fq::random_element(); - mul_right_values[j] = fq::random_element(); - expected += mul_left_values[j] * mul_right_values[j]; - mul_left.emplace_back( - fq_ct::create_from_u512_as_witness(&builder, uint512_t(uint256_t(mul_left_values[j])))); - mul_right.emplace_back( - fq_ct::create_from_u512_as_witness(&builder, uint512_t(uint256_t(mul_right_values[j])))); - to_add_values[j] = fq::random_element(); - expected += to_add_values[j]; - to_add.emplace_back( - fq_ct::create_from_u512_as_witness(&builder, uint512_t(uint256_t(to_add_values[j])))); - - // Since this test uses create_from_u512_as_witness, the tags are set to free_witness_tag - // We need to unset them so that we can test the tag propagation logic without interference - mul_left[j].unset_free_witness_tag(); - mul_right[j].unset_free_witness_tag(); - to_add[j].unset_free_witness_tag(); + expected += mul_left_native[j] * mul_right_native[j]; + expected += to_add_native[j]; } - mul_left[number_of_madds - 1].set_origin_tag(submitted_value_origin_tag); - mul_right[number_of_madds - 1].set_origin_tag(challenge_origin_tag); - to_add[number_of_madds - 1].set_origin_tag(next_challenge_tag); - uint64_t before = builder.get_estimated_num_finalized_gates(); - fq_ct f = fq_ct::mult_madd(mul_left, mul_right, to_add); - // mult_madd merges tags - EXPECT_EQ(f.get_origin_tag(), first_second_third_merged_tag); - uint64_t after = builder.get_estimated_num_finalized_gates(); - if (i == num_repetitions - 1) { - std::cerr << "num gates with mult_madd = " << after - before << std::endl; - benchmark_info(Builder::NAME_STRING, "Bigfield", "MULT_MADD", "Gate Count", after - before); - } - expected = expected.from_montgomery_form(); - uint512_t result = f.get_value(); + uint512_t result = f_ct.get_value(); EXPECT_EQ(result.lo.data[0], expected.data[0]); EXPECT_EQ(result.lo.data[1], expected.data[1]); @@ -342,59 +347,47 @@ template class stdlib_bigfield : public testing::Test { size_t num_repetitions = 1; const size_t number_of_madds = 16; for (size_t i = 0; i < num_repetitions; ++i) { - fq mul_left_values[number_of_madds]; - fq mul_right_values[number_of_madds]; - fq to_add_values[number_of_madds]; - - fq expected(0); + // Multiplicands are constants + auto [mul_left_native, mul_left_ct] = + get_random_constants(&builder, number_of_madds); // std::vector, std::vector + auto [mul_right_native, mul_right_ct] = + get_random_constants(&builder, number_of_madds); // std::vector, std::vector - std::vector mul_left; - std::vector mul_right; - std::vector to_add; - mul_left.reserve(number_of_madds); - mul_right.reserve(number_of_madds); - to_add.reserve(number_of_madds); + // Set tags of the multiplicands for (size_t j = 0; j < number_of_madds; j++) { - mul_left_values[j] = fq::random_element(); - mul_right_values[j] = fq::random_element(); - expected += mul_left_values[j] * mul_right_values[j]; - - // Left and right multiplicands are constants - fq_ct left_const = fq_ct(&builder, uint256_t(mul_left_values[j])); - fq_ct right_const = fq_ct(&builder, uint256_t(mul_right_values[j])); - mul_left.emplace_back(left_const); - mul_right.emplace_back(right_const); - mul_left[j].set_origin_tag(submitted_value_origin_tag); - mul_right[j].set_origin_tag(challenge_origin_tag); - - // to_add are witnesses - to_add_values[j] = fq::random_element(); - expected += to_add_values[j]; - to_add.emplace_back( - fq_ct::create_from_u512_as_witness(&builder, uint512_t(uint256_t(to_add_values[j])))); - - // Since this test uses create_from_u512_as_witness, the tags are set to free_witness_tag - // We need to unset them so that we can test the tag propagation logic without interference - to_add[j].unset_free_witness_tag(); + mul_left_ct[j].set_origin_tag(submitted_value_origin_tag); + mul_right_ct[j].set_origin_tag(challenge_origin_tag); } - uint64_t before = builder.get_estimated_num_finalized_gates(); - fq_ct f = fq_ct::mult_madd(mul_left, mul_right, to_add); + // to_add (summands) are witnesses + auto [to_add_native, to_add_ct] = + get_random_witnesses(&builder, number_of_madds); // std::vector, std::vector + + fq_ct f_ct; + if (i == num_repetitions - 1) { + BENCH_GATE_COUNT_START(builder, "MULT_MADD"); + f_ct = fq_ct::mult_madd(mul_left_ct, mul_right_ct, to_add_ct); + BENCH_GATE_COUNT_END(builder, "MULT_MADD"); + } else { + f_ct = fq_ct::mult_madd(mul_left_ct, mul_right_ct, to_add_ct); + } // result might not be reduced, so we need to reduce it - f.self_reduce(); + f_ct.self_reduce(); // mult_madd merges tags - EXPECT_EQ(f.get_origin_tag(), first_two_merged_tag); - uint64_t after = builder.get_estimated_num_finalized_gates(); - if (i == num_repetitions - 1) { - std::cerr << "num gates with mult_madd = " << after - before << std::endl; - benchmark_info(Builder::NAME_STRING, "Bigfield", "MULT_MADD", "Gate Count", after - before); - } + EXPECT_EQ(f_ct.get_origin_tag(), first_two_merged_tag); + // Compute expected value + fq expected(0); + for (size_t j = 0; j < number_of_madds; j++) { + expected += mul_left_native[j] * mul_right_native[j]; + expected += to_add_native[j]; + } + // Reduce the expected value to match the circuit's reduction expected = expected.reduce_once().reduce_once(); expected = expected.from_montgomery_form(); - uint512_t result = f.get_value(); + uint512_t result = f_ct.get_value(); EXPECT_EQ(result.lo.data[0], expected.data[0]); EXPECT_EQ(result.lo.data[1], expected.data[1]); @@ -417,40 +410,31 @@ template class stdlib_bigfield : public testing::Test { auto builder = Builder(); size_t num_repetitions = 1; for (size_t i = 0; i < num_repetitions; ++i) { - fq inputs[5]{ -1, -2, -3, -4, -5 }; - fq_ct a(witness_ct(&builder, fr(uint256_t(inputs[0]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[0]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - fq_ct b(witness_ct(&builder, fr(uint256_t(inputs[1]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[1]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - - fq_ct c(witness_ct(&builder, fr(uint256_t(inputs[2]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[2]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - - fq_ct d(witness_ct(&builder, fr(uint256_t(inputs[3]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[3]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - fq_ct e(witness_ct(&builder, fr(uint256_t(inputs[4]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[4]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - - a.set_origin_tag(submitted_value_origin_tag); - d.set_origin_tag(challenge_origin_tag); - e.set_origin_tag(next_challenge_tag); - uint64_t before = builder.get_estimated_num_finalized_gates(); - fq_ct f = fq_ct::dual_madd(a, b, c, d, { e }); - // dual_madd merges tags - EXPECT_EQ(f.get_origin_tag(), first_second_third_merged_tag); - uint64_t after = builder.get_estimated_num_finalized_gates(); + auto [a_native, a_ct] = get_random_witness(&builder); // fq, fq_ct + auto [b_native, b_ct] = get_random_witness(&builder); // fq, fq_ct + auto [c_native, c_ct] = get_random_witness(&builder); // fq, fq_ct + auto [d_native, d_ct] = get_random_witness(&builder); // fq, fq_ct + auto [e_native, e_ct] = get_random_witness(&builder); // fq, fq_ct + + a_ct.set_origin_tag(submitted_value_origin_tag); + d_ct.set_origin_tag(challenge_origin_tag); + e_ct.set_origin_tag(next_challenge_tag); + + fq_ct f_ct; if (i == num_repetitions - 1) { - std::cerr << "num gates per mul = " << after - before << std::endl; + BENCH_GATE_COUNT_START(builder, "DUAL_MADD"); + f_ct = fq_ct::dual_madd(a_ct, b_ct, c_ct, d_ct, { e_ct }); + BENCH_GATE_COUNT_END(builder, "DUAL_MADD"); + } else { + f_ct = fq_ct::dual_madd(a_ct, b_ct, c_ct, d_ct, { e_ct }); } - fq expected = (inputs[0] * inputs[1]) + (inputs[2] * inputs[3]) + inputs[4]; + // dual_madd merges tags + EXPECT_EQ(f_ct.get_origin_tag(), first_second_third_merged_tag); + + fq expected = (a_native * b_native) + (c_native * d_native) + e_native; expected = expected.from_montgomery_form(); - uint512_t result = f.get_value(); + uint512_t result = f_ct.get_value(); EXPECT_EQ(result.lo.data[0], expected.data[0]); EXPECT_EQ(result.lo.data[1], expected.data[1]); @@ -473,30 +457,28 @@ template class stdlib_bigfield : public testing::Test { auto builder = Builder(); size_t num_repetitions = 10; for (size_t i = 0; i < num_repetitions; ++i) { - fq inputs[3]{ fq::random_element(), fq::random_element(), fq::random_element() }; - inputs[0] = inputs[0].reduce_once().reduce_once(); - inputs[1] = inputs[1].reduce_once().reduce_once(); - fq_ct a(witness_ct(&builder, fr(uint256_t(inputs[0]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[0]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - fq_ct b(witness_ct(&builder, fr(uint256_t(inputs[1]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[1]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - a.set_origin_tag(submitted_value_origin_tag); - b.set_origin_tag(challenge_origin_tag); - uint64_t before = builder.get_estimated_num_finalized_gates(); - fq_ct c = a / b; - EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag); - uint64_t after = builder.get_estimated_num_finalized_gates(); + // We need reduced inputs for division. + auto [a_native, a_ct] = get_random_witness(&builder, true); // reduced fq, fq_ct + auto [b_native, b_ct] = get_random_witness(&builder, true); // reduced fq, fq_ct + a_ct.set_origin_tag(submitted_value_origin_tag); + b_ct.set_origin_tag(challenge_origin_tag); + + fq_ct c_ct; if (i == num_repetitions - 1) { - std::cout << "num gates per div = " << after - before << std::endl; - benchmark_info(Builder::NAME_STRING, "Bigfield", "DIV", "Gate Count", after - before); + BENCH_GATE_COUNT_START(builder, "DIV"); + c_ct = a_ct / b_ct; + BENCH_GATE_COUNT_END(builder, "DIV"); + } else { + c_ct = a_ct / b_ct; } - fq expected = (inputs[0] / inputs[1]); + // Division merges tags + EXPECT_EQ(c_ct.get_origin_tag(), first_two_merged_tag); + + fq expected = (a_native / b_native); expected = expected.reduce_once().reduce_once(); expected = expected.from_montgomery_form(); - uint512_t result = c.get_value(); + uint512_t result = c_ct.get_value(); EXPECT_EQ(result.lo.data[0], expected.data[0]); EXPECT_EQ(result.lo.data[1], expected.data[1]); @@ -516,32 +498,30 @@ template class stdlib_bigfield : public testing::Test { auto builder = Builder(); size_t num_repetitions = 10; for (size_t i = 0; i < num_repetitions; ++i) { - fq inputs[3]{ fq::random_element(), fq::random_element(), fq::random_element() }; - inputs[0] = inputs[0].reduce_once().reduce_once(); - inputs[1] = inputs[1].reduce_once().reduce_once(); - inputs[2] = inputs[2].reduce_once().reduce_once(); - - // numerator is witness, denominator is constant - fq_ct a(witness_ct(&builder, fr(uint256_t(inputs[0]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[0]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - fq_ct b(&builder, uint256_t(inputs[1])); - - a.set_origin_tag(submitted_value_origin_tag); - b.set_origin_tag(challenge_origin_tag); - uint64_t before = builder.get_estimated_num_finalized_gates(); - fq_ct c = a / b; - EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag); - uint64_t after = builder.get_estimated_num_finalized_gates(); + auto [a_native, a_ct] = get_random_witness(&builder, true); // reduced fq, fq_ct + auto [b_native, b_ct] = get_random_constant(&builder, true); // reduced fq, fq_ct + auto [c_native, c_ct] = get_random_constant(&builder, true); // reduced fq, fq_ct + a_ct.set_origin_tag(submitted_value_origin_tag); + b_ct.set_origin_tag(challenge_origin_tag); + c_ct.set_origin_tag(next_challenge_tag); + + // numerator is witness and denominator is constant. + fq_ct d_ct; if (i == num_repetitions - 1) { - std::cout << "num gates per div of witness/constant = " << after - before << std::endl; - benchmark_info(Builder::NAME_STRING, "Bigfield", "DIV_CONSTANT", "Gate Count", after - before); + BENCH_GATE_COUNT_START(builder, "DIV_WIT_BY_CONST"); + d_ct = a_ct / b_ct; + BENCH_GATE_COUNT_END(builder, "DIV_WIT_BY_CONST"); + } else { + d_ct = a_ct / b_ct; } - fq expected = (inputs[0] / inputs[1]); + // Division merges tags + EXPECT_EQ(d_ct.get_origin_tag(), first_two_merged_tag); + + fq expected = (a_native / b_native); expected = expected.reduce_once().reduce_once(); expected = expected.from_montgomery_form(); - uint512_t result = c.get_value(); + uint512_t result = d_ct.get_value(); EXPECT_EQ(result.lo.data[0], expected.data[0]); EXPECT_EQ(result.lo.data[1], expected.data[1]); @@ -553,21 +533,22 @@ template class stdlib_bigfield : public testing::Test { EXPECT_EQ(result.hi.data[3], 0ULL); // numerator is constant, denominator is witness - fq_ct e(&builder, uint256_t(inputs[2])); - e.set_origin_tag(challenge_origin_tag); - uint64_t before_e = builder.get_estimated_num_finalized_gates(); - fq_ct d = e / a; - EXPECT_EQ(d.get_origin_tag(), first_two_merged_tag); - uint64_t after_e = builder.get_estimated_num_finalized_gates(); + fq_ct e_ct; if (i == num_repetitions - 1) { - std::cout << "num gates per div of constant/witness = " << after_e - before_e << std::endl; - benchmark_info(Builder::NAME_STRING, "Bigfield", "DIV_CONSTANT", "Gate Count", after_e - before_e); + BENCH_GATE_COUNT_START(builder, "DIV_CONST_BY_WIT"); + e_ct = c_ct / a_ct; + BENCH_GATE_COUNT_END(builder, "DIV_CONST_BY_WIT"); + } else { + e_ct = c_ct / a_ct; } - fq expected_e = (inputs[2] / inputs[0]); + // Division merges tags + EXPECT_EQ(e_ct.get_origin_tag(), first_and_third_merged_tag); + + fq expected_e = (c_native / a_native); expected_e = expected_e.reduce_once().reduce_once(); expected_e = expected_e.from_montgomery_form(); - uint512_t result_e = d.get_value(); + uint512_t result_e = e_ct.get_value(); EXPECT_EQ(result_e.lo.data[0], expected_e.data[0]); EXPECT_EQ(result_e.lo.data[1], expected_e.data[1]); @@ -587,26 +568,19 @@ template class stdlib_bigfield : public testing::Test { auto builder = Builder(); size_t num_repetitions = 1; for (size_t i = 0; i < num_repetitions; ++i) { - fq inputs[4]{ fq::random_element(), fq::random_element(), fq::random_element() }; - fq_ct a(witness_ct(&builder, fr(uint256_t(inputs[0]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[0]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - fq_ct b(witness_ct(&builder, fr(uint256_t(inputs[1]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[1]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - fq_ct c(witness_ct(&builder, fr(uint256_t(inputs[2]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[2]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - fq_ct d(witness_ct(&builder, fr(uint256_t(inputs[3]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[3]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - b.set_origin_tag(submitted_value_origin_tag); - c.set_origin_tag(challenge_origin_tag); - d.set_origin_tag(next_challenge_tag); - fq_ct e = (a + b) / (c + d); + + auto [a_native, a_ct] = get_random_witness(&builder); // fq, fq_ct + auto [b_native, b_ct] = get_random_witness(&builder); // fq, fq_ct + auto [c_native, c_ct] = get_random_witness(&builder); // fq, fq_ct + auto [d_native, d_ct] = get_random_witness(&builder); // fq, fq_ct + b_ct.set_origin_tag(submitted_value_origin_tag); + c_ct.set_origin_tag(challenge_origin_tag); + d_ct.set_origin_tag(next_challenge_tag); + + fq_ct e = (a_ct + b_ct) / (c_ct + d_ct); EXPECT_EQ(e.get_origin_tag(), first_second_third_merged_tag); - fq expected = (inputs[0] + inputs[1]) / (inputs[2] + inputs[3]); + fq expected = (a_native + b_native) / (c_native + d_native); expected = expected.reduce_once().reduce_once(); expected = expected.from_montgomery_form(); uint512_t result = e.get_value(); @@ -629,29 +603,19 @@ template class stdlib_bigfield : public testing::Test { auto builder = Builder(); size_t num_repetitions = 10; for (size_t i = 0; i < num_repetitions; ++i) { - fq inputs[4]{ fq::random_element(), fq::random_element(), fq::random_element(), fq::random_element() }; - - fq_ct a(witness_ct(&builder, fr(uint256_t(inputs[0]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[0]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - fq_ct b(witness_ct(&builder, fr(uint256_t(inputs[1]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[1]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - fq_ct c(witness_ct(&builder, fr(uint256_t(inputs[2]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[2]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - fq_ct d(witness_ct(&builder, fr(uint256_t(inputs[3]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[3]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - - b.set_origin_tag(submitted_value_origin_tag); - c.set_origin_tag(challenge_origin_tag); - d.set_origin_tag(next_challenge_tag); - - fq_ct e = (a + b) * (c + d); + + auto [a_native, a_ct] = get_random_witness(&builder); // fq, fq_ct + auto [b_native, b_ct] = get_random_witness(&builder); // fq, fq_ct + auto [c_native, c_ct] = get_random_witness(&builder); // fq, fq_ct + auto [d_native, d_ct] = get_random_witness(&builder); // fq, fq_ct + b_ct.set_origin_tag(submitted_value_origin_tag); + c_ct.set_origin_tag(challenge_origin_tag); + d_ct.set_origin_tag(next_challenge_tag); + + fq_ct e = (a_ct + b_ct) * (c_ct + d_ct); EXPECT_EQ(e.get_origin_tag(), first_second_third_merged_tag); - fq expected = (inputs[0] + inputs[1]) * (inputs[2] + inputs[3]); + fq expected = (a_native + b_native) * (c_native + d_native); expected = expected.from_montgomery_form(); uint512_t result = e.get_value(); @@ -673,25 +637,18 @@ template class stdlib_bigfield : public testing::Test { auto builder = Builder(); size_t num_repetitions = 10; for (size_t i = 0; i < num_repetitions; ++i) { - fq inputs[2]{ fq::random_element(), fq::random_element() }; - uint256_t constants[2]{ engine.get_random_uint256() % fq_ct::modulus, - engine.get_random_uint256() % fq_ct::modulus }; - fq_ct a(witness_ct(&builder, fr(uint256_t(inputs[0]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[0]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - fq_ct b(&builder, constants[0]); - fq_ct c(witness_ct(&builder, fr(uint256_t(inputs[1]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[1]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - fq_ct d(&builder, constants[1]); - b.set_origin_tag(submitted_value_origin_tag); - c.set_origin_tag(challenge_origin_tag); - d.set_origin_tag(next_challenge_tag); - - fq_ct e = (a + b) * (c + d); + auto [a_native, a_ct] = get_random_witness(&builder); // fq, fq_ct + auto [b_native, b_ct] = get_random_constant(&builder); // fq, fq_ct + auto [c_native, c_ct] = get_random_witness(&builder); // fq, fq_ct + auto [d_native, d_ct] = get_random_constant(&builder); // fq, fq_ct + b_ct.set_origin_tag(submitted_value_origin_tag); + c_ct.set_origin_tag(challenge_origin_tag); + d_ct.set_origin_tag(next_challenge_tag); + + fq_ct e = (a_ct + b_ct) * (c_ct + d_ct); EXPECT_EQ(e.get_origin_tag(), first_second_third_merged_tag); - fq expected = (inputs[0] + constants[0]) * (inputs[1] + constants[1]); + fq expected = (a_native + b_native) * (c_native + d_native); expected = expected.from_montgomery_form(); uint512_t result = e.get_value(); @@ -713,29 +670,18 @@ template class stdlib_bigfield : public testing::Test { auto builder = Builder(); size_t num_repetitions = 10; for (size_t i = 0; i < num_repetitions; ++i) { - fq inputs[4]{ fq::random_element(), fq::random_element(), fq::random_element(), fq::random_element() }; - - fq_ct a(witness_ct(&builder, fr(uint256_t(inputs[0]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[0]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - fq_ct b(witness_ct(&builder, fr(uint256_t(inputs[1]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[1]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - fq_ct c(witness_ct(&builder, fr(uint256_t(inputs[2]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[2]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - fq_ct d(witness_ct(&builder, fr(uint256_t(inputs[3]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[3]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - - b.set_origin_tag(submitted_value_origin_tag); - c.set_origin_tag(challenge_origin_tag); - d.set_origin_tag(next_challenge_tag); - - fq_ct e = (a - b) * (c - d); + auto [a_native, a_ct] = get_random_witness(&builder); // fq, fq_ct + auto [b_native, b_ct] = get_random_witness(&builder); // fq, fq_ct + auto [c_native, c_ct] = get_random_witness(&builder); // fq, fq_ct + auto [d_native, d_ct] = get_random_witness(&builder); // fq, fq_ct + b_ct.set_origin_tag(submitted_value_origin_tag); + c_ct.set_origin_tag(challenge_origin_tag); + d_ct.set_origin_tag(next_challenge_tag); + + fq_ct e = (a_ct - b_ct) * (c_ct - d_ct); EXPECT_EQ(e.get_origin_tag(), first_second_third_merged_tag); - fq expected = (inputs[0] - inputs[1]) * (inputs[2] - inputs[3]); + fq expected = (a_native - b_native) * (c_native - d_native); expected = expected.from_montgomery_form(); uint512_t result = e.get_value(); @@ -758,23 +704,19 @@ template class stdlib_bigfield : public testing::Test { size_t num_repetitions = 8; for (size_t i = 0; i < num_repetitions; ++i) { auto builder = Builder(); - auto [mul_l, mul_l_ct] = get_random_element(&builder); - auto [mul_r1, mul_r1_ct] = get_random_element(&builder); - auto [mul_r2, mul_r2_ct] = get_random_element(&builder); - auto [divisor1, divisor1_ct] = get_random_element(&builder); - auto [divisor2, divisor2_ct] = get_random_element(&builder); - auto [to_sub1, to_sub1_ct] = get_random_element(&builder); - auto [to_sub2, to_sub2_ct] = get_random_element(&builder); + auto [mul_l, mul_l_ct] = get_random_witness(&builder); + auto [mul_r1, mul_r1_ct] = get_random_witness(&builder); + auto [mul_r2, mul_r2_ct] = get_random_witness(&builder); + auto [divisor1, divisor1_ct] = get_random_witness(&builder); + auto [divisor2, divisor2_ct] = get_random_witness(&builder); + auto [to_sub1, to_sub1_ct] = get_random_witness(&builder); + auto [to_sub2, to_sub2_ct] = get_random_witness(&builder); mul_l_ct.set_origin_tag(submitted_value_origin_tag); mul_r1_ct.set_origin_tag(challenge_origin_tag); divisor1_ct.set_origin_tag(next_submitted_value_origin_tag); to_sub1_ct.set_origin_tag(next_challenge_tag); - mul_r2_ct.unset_free_witness_tag(); - divisor2_ct.unset_free_witness_tag(); - to_sub2_ct.unset_free_witness_tag(); - fq_ct result_ct = fq_ct::msub_div( { mul_l_ct }, { mul_r1_ct - mul_r2_ct }, divisor1_ct - divisor2_ct, { to_sub1_ct, to_sub2_ct }); EXPECT_EQ(result_ct.get_origin_tag(), first_to_fourth_merged_tag); @@ -793,24 +735,18 @@ template class stdlib_bigfield : public testing::Test { size_t num_repetitions = 1; for (size_t i = 0; i < num_repetitions; ++i) { - fq inputs[4]{ fq::random_element(), fq::random_element(), fq::random_element(), fq::random_element() }; - - fq_ct a(witness_ct(&builder, fr(uint256_t(inputs[0]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[0]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - - a.set_origin_tag(submitted_value_origin_tag); - - typename bn254::bool_ct predicate_a(witness_ct(&builder, true)); + auto [a_native, a_ct] = get_random_witness(&builder); // fq, fq_ct + a_ct.set_origin_tag(submitted_value_origin_tag); + bool_ct predicate_a(witness_ct(&builder, true)); predicate_a.set_origin_tag(challenge_origin_tag); - fq_ct c = a.conditional_negate(predicate_a); + fq_ct c = a_ct.conditional_negate(predicate_a); // Conditional negate merges tags EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag); - fq_ct d = a.conditional_negate(!predicate_a); + fq_ct d = a_ct.conditional_negate(!predicate_a); fq_ct e = c + d; uint512_t c_out = c.get_value(); uint512_t d_out = d.get_value(); @@ -820,8 +756,8 @@ template class stdlib_bigfield : public testing::Test { fq result_d(d_out.lo); fq result_e(e_out.lo); - fq expected_c = (-inputs[0]); - fq expected_d = inputs[0]; + fq expected_c = (-a_native); + fq expected_d = a_native; EXPECT_EQ(result_c, expected_c); EXPECT_EQ(result_d, expected_d); @@ -851,13 +787,15 @@ template class stdlib_bigfield : public testing::Test { fq_ct y2( witness_ct(&builder, fr(uint256_t(P2.y).slice(0, fq_ct::NUM_LIMB_BITS * 2))), witness_ct(&builder, fr(uint256_t(P2.y).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - uint64_t before = builder.get_estimated_num_finalized_gates(); + uint64_t before = builder.get_estimated_num_finalized_gates(); fq_ct lambda = (y2 - y1) / (x2 - x1); fq_ct x3 = lambda.sqr() - (x2 + x1); fq_ct y3 = (x1 - x3) * lambda - y1; uint64_t after = builder.get_estimated_num_finalized_gates(); std::cerr << "added gates = " << after - before << std::endl; + + // Check the result against the native group addition g1::affine_element P3(g1::element(P1) + g1::element(P2)); fq expected_x = P3.x; fq expected_y = P3.y; @@ -883,31 +821,25 @@ template class stdlib_bigfield : public testing::Test { auto builder = Builder(); size_t num_repetitions = 10; for (size_t i = 0; i < num_repetitions; ++i) { + auto [a_native, a_ct] = get_random_witness(&builder); // fq, fq_ct + auto [b_native, b_ct] = get_random_witness(&builder); // fq, fq_ct - fq inputs[4]{ fq::random_element(), fq::random_element(), fq::random_element(), fq::random_element() }; - - fq_ct a(witness_ct(&builder, fr(uint256_t(inputs[0]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[0]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - fq_ct b(witness_ct(&builder, fr(uint256_t(inputs[1]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[1]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - - fq_ct c = a; - fq expected = inputs[0]; + fq_ct c_ct = a_ct; + fq expected = a_native; for (size_t i = 0; i < 16; ++i) { - c = b * b + c; - expected = inputs[1] * inputs[1] + expected; + c_ct = b_ct * b_ct + c_ct; + expected = b_native * b_native + expected; } - c.set_origin_tag(challenge_origin_tag); - // fq_ct c = a + a + a + a - b - b - b - b; - c.self_reduce(); + c_ct.set_origin_tag(challenge_origin_tag); + c_ct.self_reduce(); + // self_reduce preserves tags - EXPECT_EQ(c.get_origin_tag(), challenge_origin_tag); - fq result = fq(c.get_value().lo); + EXPECT_EQ(c_ct.get_origin_tag(), challenge_origin_tag); + + fq result = fq(c_ct.get_value().lo); EXPECT_EQ(result, expected); - EXPECT_EQ(c.get_value().get_msb() < 254, true); + EXPECT_EQ(c_ct.get_value().get_msb() < 254, true); } bool result = CircuitChecker::check(builder); EXPECT_EQ(result, true); @@ -919,26 +851,25 @@ template class stdlib_bigfield : public testing::Test { size_t num_repetitions = 10; for (size_t i = 0; i < num_repetitions; ++i) { - fq inputs[4]{ fq::random_element(), fq::random_element(), fq::random_element(), fq::random_element() }; + auto [a_native, a_ct] = get_random_witness(&builder); // fq, fq_ct + auto [b_native, b_ct] = get_random_witness(&builder); // fq, fq_ct - fq_ct a(witness_ct(&builder, fr(uint256_t(inputs[0]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[0]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - fq_ct b(witness_ct(&builder, fr(uint256_t(inputs[1]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[1]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - - fq_ct c = a; - fq expected = inputs[0]; + fq_ct c_ct = a_ct; + fq expected = a_native; for (size_t i = 0; i < 16; ++i) { - c = b * b + c; - expected = inputs[1] * inputs[1] + expected; + c_ct = b_ct * b_ct + c_ct; + expected = b_native * b_native + expected; } - // fq_ct c = a + a + a + a - b - b - b - b; - c.assert_is_in_field(); - uint256_t result = (c.get_value().lo); + + c_ct.set_origin_tag(challenge_origin_tag); + c_ct.assert_is_in_field(); + + // assert_is_in_field preserves tags + EXPECT_EQ(c_ct.get_origin_tag(), challenge_origin_tag); + + uint256_t result = (c_ct.get_value().lo); EXPECT_EQ(result, uint256_t(expected)); - EXPECT_EQ(c.get_value().get_msb() < 254, true); + EXPECT_EQ(c_ct.get_value().get_msb() < 254, true); } bool result = CircuitChecker::check(builder); EXPECT_EQ(result, true); @@ -952,42 +883,36 @@ template class stdlib_bigfield : public testing::Test { constexpr uint256_t bit_mask = (uint256_t(1) << num_bits) - 1; for (size_t i = 0; i < num_repetitions; ++i) { - fq inputs[4]{ uint256_t(fq::random_element()) && bit_mask, - uint256_t(fq::random_element()) && bit_mask, - uint256_t(fq::random_element()) && bit_mask, - uint256_t(fq::random_element()) && bit_mask }; + uint256_t a_u256 = uint256_t(fq::random_element()) && bit_mask; + uint256_t b_u256 = uint256_t(fq::random_element()) && bit_mask; - fq_ct a(witness_ct(&builder, fr(uint256_t(inputs[0]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[0]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - fq_ct b(witness_ct(&builder, fr(uint256_t(inputs[1]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[1]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); + fq_ct a_ct(witness_ct(&builder, fr(a_u256.slice(0, fq_ct::NUM_LIMB_BITS * 2))), + witness_ct(&builder, fr(a_u256.slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); + fq_ct b_ct(witness_ct(&builder, fr(b_u256.slice(0, fq_ct::NUM_LIMB_BITS * 2))), + witness_ct(&builder, fr(b_u256.slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - fq_ct c = a; - fq expected = inputs[0]; + fq_ct c_ct = a_ct; + fq expected = fq(a_u256); for (size_t i = 0; i < 16; ++i) { - c = b * b + c; - expected = inputs[1] * inputs[1] + expected; + c_ct = b_ct * b_ct + c_ct; + expected = fq(b_u256) * fq(b_u256) + expected; } - // fq_ct c = a + a + a + a - b - b - b - b; - c.assert_less_than(bit_mask + 1); - uint256_t result = (c.get_value().lo); + + c_ct.assert_less_than(bit_mask + 1); + uint256_t result = (c_ct.get_value().lo); EXPECT_EQ(result, uint256_t(expected)); - EXPECT_EQ(c.get_value().get_msb() < num_bits, true); + EXPECT_EQ(c_ct.get_value().get_msb() < num_bits, true); } bool result = CircuitChecker::check(builder); EXPECT_EQ(result, true); + // Checking edge conditions - fq random_input = fq::random_element(); - fq_ct a(witness_ct(&builder, fr(uint256_t(random_input).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(random_input).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); + auto [random_input, a_ct] = get_random_witness(&builder); - a.assert_less_than(random_input + 1); + a_ct.assert_less_than(random_input + 1); EXPECT_EQ(CircuitChecker::check(builder), true); - a.assert_less_than(random_input); + a_ct.assert_less_than(random_input); EXPECT_EQ(CircuitChecker::check(builder), false); } @@ -997,11 +922,13 @@ template class stdlib_bigfield : public testing::Test { size_t num_repetitions = 10; for (size_t i = 0; i < num_repetitions; ++i) { - fq inputs[4]{ fq::random_element(), fq::random_element(), fq::random_element(), fq::random_element() }; + fq a_native = fq::random_element(); + fq b_native = fq::random_element(); + std::vector input_a(sizeof(fq)); - fq::serialize_to_buffer(inputs[0], &input_a[0]); + fq::serialize_to_buffer(a_native, &input_a[0]); std::vector input_b(sizeof(fq)); - fq::serialize_to_buffer(inputs[1], &input_b[0]); + fq::serialize_to_buffer(b_native, &input_b[0]); stdlib::byte_array input_arr_a(&builder, input_a); stdlib::byte_array input_arr_b(&builder, input_b); @@ -1009,15 +936,15 @@ template class stdlib_bigfield : public testing::Test { input_arr_a.set_origin_tag(submitted_value_origin_tag); input_arr_b.set_origin_tag(challenge_origin_tag); - fq_ct a(input_arr_a); - fq_ct b(input_arr_b); + fq_ct a_ct(input_arr_a); + fq_ct b_ct(input_arr_b); - fq_ct c = a * b; + fq_ct c_ct = a_ct * b_ct; - EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag); + EXPECT_EQ(c_ct.get_origin_tag(), first_two_merged_tag); - fq expected = inputs[0] * inputs[1]; - uint256_t result = (c.get_value().lo); + fq expected = a_native * b_native; + uint256_t result = (c_ct.get_value().lo); EXPECT_EQ(result, uint256_t(expected)); } bool result = CircuitChecker::check(builder); @@ -1073,7 +1000,7 @@ template class stdlib_bigfield : public testing::Test { fq b(1); fq_ct a_ct(&builder, a); fq_ct b_ct(&builder, b); - fq_ct selected = a_ct.conditional_select(b_ct, typename bn254::bool_ct(&builder, true)); + fq_ct selected = a_ct.conditional_select(b_ct, bool_ct(&builder, true)); EXPECT_EQ(fq((selected.get_value() % uint512_t(bb::fq::modulus)).lo), b); } @@ -1104,33 +1031,22 @@ template class stdlib_bigfield : public testing::Test { auto builder = Builder(); size_t num_repetitions = 10; for (size_t i = 0; i < num_repetitions; ++i) { - fq inputs[4]{ fq::random_element(), fq::random_element(), fq::random_element(), fq::random_element() }; - - fq_ct a(witness_ct(&builder, fr(uint256_t(inputs[0]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[0]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - fq_ct b(witness_ct(&builder, fr(uint256_t(inputs[1]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[1]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - fq_ct c(witness_ct(&builder, fr(uint256_t(inputs[2]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[2]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - fq_ct d(witness_ct(&builder, fr(uint256_t(inputs[3]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&builder, - fr(uint256_t(inputs[3]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - - fq_ct two = fq_ct::unsafe_construct_from_limbs(witness_ct(&builder, fr(2)), - witness_ct(&builder, fr(0)), - witness_ct(&builder, fr(0)), - witness_ct(&builder, fr(0))); - fq_ct t0 = a + a; - fq_ct t1 = a * two; + auto [a_native, a_ct] = get_random_witness(&builder); // fq, fq_ct + auto [c_native, c_ct] = get_random_witness(&builder); // fq, fq_ct + auto [d_native, d_ct] = get_random_witness(&builder); // fq, fq_ct + + fq_ct two_ct = fq_ct::unsafe_construct_from_limbs(witness_ct(&builder, fr(2)), + witness_ct(&builder, fr(0)), + witness_ct(&builder, fr(0)), + witness_ct(&builder, fr(0))); + fq_ct t0 = a_ct + a_ct; + fq_ct t1 = a_ct * two_ct; t0.assert_equal(t1); - t0.assert_is_not_equal(c); - t0.assert_is_not_equal(d); + t0.assert_is_not_equal(c_ct); + t0.assert_is_not_equal(d_ct); stdlib::bool_t is_equal_a = t0 == t1; - stdlib::bool_t is_equal_b = t0 == c; + stdlib::bool_t is_equal_b = t0 == c_ct; EXPECT_TRUE(is_equal_a.get_value()); EXPECT_FALSE(is_equal_b.get_value()); } @@ -1147,10 +1063,10 @@ template class stdlib_bigfield : public testing::Test { // Set the high bit exponent_val |= static_cast(1) << 31; fq_ct base_constant(&builder, static_cast(base_val)); - auto base_witness = fq_ct::from_witness(&builder, static_cast(base_val)); + fq_ct base_witness_ct = fq_ct::from_witness(&builder, static_cast(base_val)); // This also tests for the case where the exponent is zero for (size_t i = 0; i <= 32; i += 4) { - auto current_exponent_val = exponent_val >> i; + uint32_t current_exponent_val = exponent_val >> i; fq expected = base_val.pow(current_exponent_val); // Check for constant bigfield element with constant exponent @@ -1158,10 +1074,10 @@ template class stdlib_bigfield : public testing::Test { EXPECT_EQ(fq(result_constant_base.get_value()), expected); // Check for witness base with constant exponent - fq_ct result_witness_base = base_witness.pow(current_exponent_val); + fq_ct result_witness_base = base_witness_ct.pow(current_exponent_val); EXPECT_EQ(fq(result_witness_base.get_value()), expected); - base_witness.set_origin_tag(submitted_value_origin_tag); + base_witness_ct.set_origin_tag(submitted_value_origin_tag); } bool check_result = CircuitChecker::check(builder); @@ -1175,16 +1091,16 @@ template class stdlib_bigfield : public testing::Test { fq base_val(engine.get_random_uint256()); uint32_t current_exponent_val = 1; - fq_ct base_constant(&builder, static_cast(base_val)); - auto base_witness = fq_ct::from_witness(&builder, static_cast(base_val)); + fq_ct base_constant_ct(&builder, static_cast(base_val)); + fq_ct base_witness_ct = fq_ct::from_witness(&builder, static_cast(base_val)); fq expected = base_val.pow(current_exponent_val); // Check for constant bigfield element with constant exponent - fq_ct result_constant_base = base_constant.pow(current_exponent_val); + fq_ct result_constant_base = base_constant_ct.pow(current_exponent_val); EXPECT_EQ(fq(result_constant_base.get_value()), expected); // Check for witness base with constant exponent - fq_ct result_witness_base = base_witness.pow(current_exponent_val); + fq_ct result_witness_base = base_witness_ct.pow(current_exponent_val); EXPECT_EQ(fq(result_witness_base.get_value()), expected); bool check_result = CircuitChecker::check(builder); @@ -1302,6 +1218,7 @@ template class stdlib_bigfield : public testing::Test { (void)w2; EXPECT_TRUE(CircuitChecker::check(builder)); } + static void test_assert_not_equal_regression() { auto builder = Builder(); diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield_impl.hpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield_impl.hpp index 3aee3fcee3e4..b21cb5981247 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield_impl.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield_impl.hpp @@ -89,7 +89,7 @@ bigfield::bigfield(const field_t& low_bits_in, if (!high_bits_in.is_constant()) { // Decompose the high bits into 2 limbs and range constrain them. const auto limb_witnesses = context->decompose_non_native_field_double_width_limb( - high_bits_in.get_normalized_witness_index(), (size_t)num_high_limb_bits); + high_bits_in.get_normalized_witness_index(), static_cast(num_high_limb_bits)); limb_2.witness_index = limb_witnesses[0]; limb_3.witness_index = limb_witnesses[1]; field_t::evaluate_linear_identity(high_bits_in, -limb_2, -limb_3 * shift_1, field_t(0)); @@ -125,7 +125,7 @@ bigfield::bigfield(const bigfield& other) {} template -bigfield::bigfield(bigfield&& other) +bigfield::bigfield(bigfield&& other) noexcept : context(other.context) , binary_basis_limbs{ other.binary_basis_limbs[0], other.binary_basis_limbs[1], @@ -142,7 +142,7 @@ bigfield bigfield::create_from_u512_as_witness(Builder* { ASSERT((can_overflow == true && maximum_bitlength == 0) || (can_overflow == false && (maximum_bitlength == 0 || maximum_bitlength > (3 * NUM_LIMB_BITS)))); - std::array limbs; + std::array limbs; limbs[0] = value.slice(0, NUM_LIMB_BITS).lo; limbs[1] = value.slice(NUM_LIMB_BITS, NUM_LIMB_BITS * 2).lo; limbs[2] = value.slice(NUM_LIMB_BITS * 2, NUM_LIMB_BITS * 3).lo; @@ -195,12 +195,12 @@ bigfield bigfield::create_from_u512_as_witness(Builder* result.prime_basis_limb = prime_limb; ctx->range_constrain_two_limbs(limb_0.get_normalized_witness_index(), limb_1.get_normalized_witness_index(), - (size_t)NUM_LIMB_BITS, - (size_t)NUM_LIMB_BITS); + static_cast(NUM_LIMB_BITS), + static_cast(NUM_LIMB_BITS)); ctx->range_constrain_two_limbs(limb_2.get_normalized_witness_index(), limb_3.get_normalized_witness_index(), - (size_t)NUM_LIMB_BITS, - (size_t)num_last_limb_bits); + static_cast(NUM_LIMB_BITS), + static_cast(num_last_limb_bits)); // Mark the element as coming out of nowhere result.set_free_witness_tag(); @@ -271,6 +271,9 @@ template bigfield::bigfield(const byt template bigfield& bigfield::operator=(const bigfield& other) { + if (this == &other) { + return *this; + } context = other.context; binary_basis_limbs[0] = other.binary_basis_limbs[0]; binary_basis_limbs[1] = other.binary_basis_limbs[1]; @@ -280,7 +283,7 @@ template bigfield& bigfield bigfield& bigfield::operator=(bigfield&& other) +template bigfield& bigfield::operator=(bigfield&& other) noexcept { context = other.context; binary_basis_limbs[0] = other.binary_basis_limbs[0]; @@ -311,7 +314,7 @@ template uint512_t bigfield::get_maxi template bigfield bigfield::add_to_lower_limb(const field_t& other, - uint256_t other_maximum_value) const + const uint256_t& other_maximum_value) const { reduction_check(); ASSERT((uint512_t(other_maximum_value) + uint512_t(binary_basis_limbs[0].maximum_value)) <= @@ -404,36 +407,32 @@ bigfield bigfield::operator+(const bigfield& other) cons other.prime_basis_limb.get_witness_index()); // We are comparing if the bigfield elements are exactly the // same object, so we compare the unnormalized witness indices if (!limbconst) { - std::pair x0{ binary_basis_limbs[0].element.witness_index, - binary_basis_limbs[0].element.multiplicative_constant }; - std::pair x1{ binary_basis_limbs[1].element.witness_index, - binary_basis_limbs[1].element.multiplicative_constant }; - std::pair x2{ binary_basis_limbs[2].element.witness_index, - binary_basis_limbs[2].element.multiplicative_constant }; - std::pair x3{ binary_basis_limbs[3].element.witness_index, - binary_basis_limbs[3].element.multiplicative_constant }; - std::pair y0{ other.binary_basis_limbs[0].element.witness_index, - other.binary_basis_limbs[0].element.multiplicative_constant }; - std::pair y1{ other.binary_basis_limbs[1].element.witness_index, - other.binary_basis_limbs[1].element.multiplicative_constant }; - std::pair y2{ other.binary_basis_limbs[2].element.witness_index, - other.binary_basis_limbs[2].element.multiplicative_constant }; - std::pair y3{ other.binary_basis_limbs[3].element.witness_index, - other.binary_basis_limbs[3].element.multiplicative_constant }; - bb::fr c0(binary_basis_limbs[0].element.additive_constant + - other.binary_basis_limbs[0].element.additive_constant); - bb::fr c1(binary_basis_limbs[1].element.additive_constant + - other.binary_basis_limbs[1].element.additive_constant); - bb::fr c2(binary_basis_limbs[2].element.additive_constant + - other.binary_basis_limbs[2].element.additive_constant); - bb::fr c3(binary_basis_limbs[3].element.additive_constant + - other.binary_basis_limbs[3].element.additive_constant); - - uint32_t xp(prime_basis_limb.witness_index); - uint32_t yp(other.prime_basis_limb.witness_index); - bb::fr cp(prime_basis_limb.additive_constant + other.prime_basis_limb.additive_constant); - const auto output_witnesses = ctx->evaluate_non_native_field_addition( - { x0, y0, c0 }, { x1, y1, c1 }, { x2, y2, c2 }, { x3, y3, c3 }, { xp, yp, cp }); + // Extract witness indices and multiplicative constants for binary basis limbs + std::array, NUM_LIMBS> x_scaled; + std::array, NUM_LIMBS> y_scaled; + std::array c_adds; + + for (size_t i = 0; i < NUM_LIMBS; ++i) { + const auto& x_limb = binary_basis_limbs[i].element; + const auto& y_limb = other.binary_basis_limbs[i].element; + + x_scaled[i] = { x_limb.witness_index, x_limb.multiplicative_constant }; + y_scaled[i] = { y_limb.witness_index, y_limb.multiplicative_constant }; + c_adds[i] = bb::fr(x_limb.additive_constant + y_limb.additive_constant); + } + + // Extract witness indices for prime basis limb + uint32_t x_prime(prime_basis_limb.witness_index); + uint32_t y_prime(other.prime_basis_limb.witness_index); + bb::fr c_prime(prime_basis_limb.additive_constant + other.prime_basis_limb.additive_constant); + + const auto output_witnesses = + ctx->evaluate_non_native_field_addition({ x_scaled[0], y_scaled[0], c_adds[0] }, + { x_scaled[1], y_scaled[1], c_adds[1] }, + { x_scaled[2], y_scaled[2], c_adds[2] }, + { x_scaled[3], y_scaled[3], c_adds[3] }, + { x_prime, y_prime, c_prime }); + result.binary_basis_limbs[0].element = field_t::from_witness_index(ctx, output_witnesses[0]); result.binary_basis_limbs[1].element = field_t::from_witness_index(ctx, output_witnesses[1]); result.binary_basis_limbs[2].element = field_t::from_witness_index(ctx, output_witnesses[2]); @@ -633,8 +632,10 @@ bigfield bigfield::operator-(const bigfield& other) cons result.binary_basis_limbs[2].element = binary_basis_limbs[2].element + bb::fr(to_add_2); result.binary_basis_limbs[3].element = binary_basis_limbs[3].element + bb::fr(to_add_3); - if (prime_basis_limb.multiplicative_constant == 1 && other.prime_basis_limb.multiplicative_constant == 1 && - !is_constant() && !other.is_constant()) { + bool both_witness = !is_constant() && !other.is_constant(); + bool both_prime_limb_multiplicative_constant_one = + (prime_basis_limb.multiplicative_constant == 1 && other.prime_basis_limb.multiplicative_constant == 1); + if (both_prime_limb_multiplicative_constant_one && both_witness) { bool limbconst = result.binary_basis_limbs[0].element.is_constant(); limbconst = limbconst || result.binary_basis_limbs[1].element.is_constant(); limbconst = limbconst || result.binary_basis_limbs[2].element.is_constant(); @@ -650,39 +651,33 @@ bigfield bigfield::operator-(const bigfield& other) cons other.prime_basis_limb.witness_index); // We are checking if this is and identical element, so we // need to compare the actual indices, not normalized ones if (!limbconst) { - std::pair x0{ result.binary_basis_limbs[0].element.witness_index, - binary_basis_limbs[0].element.multiplicative_constant }; - std::pair x1{ result.binary_basis_limbs[1].element.witness_index, - binary_basis_limbs[1].element.multiplicative_constant }; - std::pair x2{ result.binary_basis_limbs[2].element.witness_index, - binary_basis_limbs[2].element.multiplicative_constant }; - std::pair x3{ result.binary_basis_limbs[3].element.witness_index, - binary_basis_limbs[3].element.multiplicative_constant }; - std::pair y0{ other.binary_basis_limbs[0].element.witness_index, - other.binary_basis_limbs[0].element.multiplicative_constant }; - std::pair y1{ other.binary_basis_limbs[1].element.witness_index, - other.binary_basis_limbs[1].element.multiplicative_constant }; - std::pair y2{ other.binary_basis_limbs[2].element.witness_index, - other.binary_basis_limbs[2].element.multiplicative_constant }; - std::pair y3{ other.binary_basis_limbs[3].element.witness_index, - other.binary_basis_limbs[3].element.multiplicative_constant }; - bb::fr c0(result.binary_basis_limbs[0].element.additive_constant - - other.binary_basis_limbs[0].element.additive_constant); - bb::fr c1(result.binary_basis_limbs[1].element.additive_constant - - other.binary_basis_limbs[1].element.additive_constant); - bb::fr c2(result.binary_basis_limbs[2].element.additive_constant - - other.binary_basis_limbs[2].element.additive_constant); - bb::fr c3(result.binary_basis_limbs[3].element.additive_constant - - other.binary_basis_limbs[3].element.additive_constant); - - uint32_t xp(prime_basis_limb.witness_index); - uint32_t yp(other.prime_basis_limb.witness_index); - bb::fr cp(prime_basis_limb.additive_constant - other.prime_basis_limb.additive_constant); - uint512_t constant_to_add_mod_p = (constant_to_add) % prime_basis.modulus; - cp += bb::fr(constant_to_add_mod_p.lo); - - const auto output_witnesses = ctx->evaluate_non_native_field_subtraction( - { x0, y0, c0 }, { x1, y1, c1 }, { x2, y2, c2 }, { x3, y3, c3 }, { xp, yp, cp }); + // Extract witness indices and multiplicative constants for binary basis limbs + std::array, NUM_LIMBS> x_scaled; + std::array, NUM_LIMBS> y_scaled; + std::array c_diffs; + + for (size_t i = 0; i < NUM_LIMBS; ++i) { + const auto& x_limb = result.binary_basis_limbs[i].element; + const auto& y_limb = other.binary_basis_limbs[i].element; + + x_scaled[i] = { x_limb.witness_index, x_limb.multiplicative_constant }; + y_scaled[i] = { y_limb.witness_index, y_limb.multiplicative_constant }; + c_diffs[i] = bb::fr(x_limb.additive_constant - y_limb.additive_constant); + } + + // Extract witness indices for prime basis limb + uint32_t x_prime(prime_basis_limb.witness_index); + uint32_t y_prime(other.prime_basis_limb.witness_index); + bb::fr c_prime(prime_basis_limb.additive_constant - other.prime_basis_limb.additive_constant); + uint512_t constant_to_add_mod_native = (constant_to_add) % prime_basis.modulus; + c_prime += bb::fr(constant_to_add_mod_native.lo); + + const auto output_witnesses = + ctx->evaluate_non_native_field_subtraction({ x_scaled[0], y_scaled[0], c_diffs[0] }, + { x_scaled[1], y_scaled[1], c_diffs[1] }, + { x_scaled[2], y_scaled[2], c_diffs[2] }, + { x_scaled[3], y_scaled[3], c_diffs[3] }, + { x_prime, y_prime, c_prime }); result.binary_basis_limbs[0].element = field_t::from_witness_index(ctx, output_witnesses[0]); result.binary_basis_limbs[1].element = field_t::from_witness_index(ctx, output_witnesses[1]); @@ -703,8 +698,8 @@ bigfield bigfield::operator-(const bigfield& other) cons /** * Compute the prime basis limb of the result **/ - uint512_t constant_to_add_mod_p = (constant_to_add) % prime_basis.modulus; - field_t prime_basis_to_add(ctx, bb::fr(constant_to_add_mod_p.lo)); + uint512_t constant_to_add_mod_native = (constant_to_add) % prime_basis.modulus; + field_t prime_basis_to_add(ctx, bb::fr(constant_to_add_mod_native.lo)); result.prime_basis_limb = prime_basis_limb + prime_basis_to_add; result.prime_basis_limb -= other.prime_basis_limb; return result; @@ -802,7 +797,7 @@ bigfield bigfield::internal_div(const std::vector(denominator.get_context(), uint256_t(0)); } @@ -843,6 +838,8 @@ bigfield bigfield::internal_div(const std::vector numerator_max; for (const auto& n : numerators) { @@ -1118,8 +1115,6 @@ bigfield bigfield::madd(const bigfield& to_mul, const st return remainder; } -// MERGENOTE: Implementing dual_madd in terms of mult_madd following #729 - /** * @brief Performs individual reductions on the supplied elements as well as more complex reductions to prevent CRT * modulus overflow and to fit the quotient inside the range proof @@ -1164,8 +1159,7 @@ void bigfield::perform_reductions_for_mult_madd(std::vector( + bool reduction_required = std::get<0>( get_quotient_reduction_info(max_values_left, max_values_right, to_add, { DEFAULT_MAXIMUM_REMAINDER })); if (reduction_required) { @@ -1610,122 +1604,78 @@ bigfield bigfield::conditional_negate(const bool_t>(predicate).madd(-(binary_basis_limbs[0].element * two) + to_add_0, - binary_basis_limbs[0].element); - field_t limb_1 = static_cast>(predicate).madd(-(binary_basis_limbs[1].element * two) + to_add_1, - binary_basis_limbs[1].element); - field_t limb_2 = static_cast>(predicate).madd(-(binary_basis_limbs[2].element * two) + to_add_2, - binary_basis_limbs[2].element); - field_t limb_3 = static_cast>(predicate).madd(-(binary_basis_limbs[3].element * two) + to_add_3, - binary_basis_limbs[3].element); - - uint256_t maximum_negated_limb_0 = to_add_0_u256 + t0; - uint256_t maximum_negated_limb_1 = to_add_1_u256 + t1; - uint256_t maximum_negated_limb_2 = to_add_2_u256 + t2; - uint256_t maximum_negated_limb_3 = to_add_3_u256; - - uint256_t max_limb_0 = binary_basis_limbs[0].maximum_value > maximum_negated_limb_0 - ? binary_basis_limbs[0].maximum_value - : maximum_negated_limb_0; - uint256_t max_limb_1 = binary_basis_limbs[1].maximum_value > maximum_negated_limb_1 - ? binary_basis_limbs[1].maximum_value - : maximum_negated_limb_1; - uint256_t max_limb_2 = binary_basis_limbs[2].maximum_value > maximum_negated_limb_2 - ? binary_basis_limbs[2].maximum_value - : maximum_negated_limb_2; - uint256_t max_limb_3 = binary_basis_limbs[3].maximum_value > maximum_negated_limb_3 - ? binary_basis_limbs[3].maximum_value - : maximum_negated_limb_3; - - bigfield result(ctx); - result.binary_basis_limbs[0] = Limb(limb_0, max_limb_0); - result.binary_basis_limbs[1] = Limb(limb_1, max_limb_1); - result.binary_basis_limbs[2] = Limb(limb_2, max_limb_2); - result.binary_basis_limbs[3] = Limb(limb_3, max_limb_3); - - uint512_t constant_to_add_mod_p = constant_to_add % prime_basis.modulus; - field_t prime_basis_to_add(ctx, bb::fr(constant_to_add_mod_p.lo)); - result.prime_basis_limb = - static_cast>(predicate).madd(-(prime_basis_limb * two) + prime_basis_to_add, prime_basis_limb); - - result.set_origin_tag(OriginTag(get_origin_tag(), predicate.tag)); + // We want to check: + // predicate = 1 ==> (0 - *this) + // predicate = 0 ==> *this + // + // We just use the conditional_assign method to do this as it costs the same number of gates as computing + // p * (0 - *this) + (1 - p) * (*this) + // + bigfield negative_this = zero() - *this; + bigfield result = bigfield::conditional_assign(predicate, negative_this, *this); return result; } -/** - * @brief Create an element which is equal to either this or other based on the predicate - * - * @tparam Builder - * @tparam T - * @param other The other bigfield element - * @param predicate Predicate controlling the result (0 for this, 1 for the other) - * @return Resulting element - */ template bigfield bigfield::conditional_select(const bigfield& other, const bool_t& predicate) const { - if (is_constant() && other.is_constant() && predicate.is_constant()) { + // If the predicate is constant, the conditional selection can be done out of circuit + if (predicate.is_constant()) { if (predicate.get_value()) { return other; } return *this; } + + // If both elements are the same, we can just return one of them + auto is_limb_same = [](const field_ct& a, const field_ct& b) { + const bool is_witness_index_same = a.get_witness_index() == b.get_witness_index(); + const bool is_add_constant_same = a.additive_constant == b.additive_constant; + const bool is_mul_constant_same = a.multiplicative_constant == b.multiplicative_constant; + return is_witness_index_same && is_add_constant_same && is_mul_constant_same; + }; + + bool is_limb_0_same = is_limb_same(binary_basis_limbs[0].element, other.binary_basis_limbs[0].element); + bool is_limb_1_same = is_limb_same(binary_basis_limbs[1].element, other.binary_basis_limbs[1].element); + bool is_limb_2_same = is_limb_same(binary_basis_limbs[2].element, other.binary_basis_limbs[2].element); + bool is_limb_3_same = is_limb_same(binary_basis_limbs[3].element, other.binary_basis_limbs[3].element); + bool is_prime_limb_same = is_limb_same(prime_basis_limb, other.prime_basis_limb); + if (is_limb_0_same && is_limb_1_same && is_limb_2_same && is_limb_3_same && is_prime_limb_same) { + return *this; + } + Builder* ctx = context ? context : (other.context ? other.context : predicate.context); - // TODO(https://github.com/AztecProtocol/aztec-packages/issues/14657): use field_t::conditional_assign method - field_t binary_limb_0 = static_cast>(predicate).madd( + // For each limb, we must select: + // `this` if predicate == 0 + // `other` if predicate == 1 + // + // Thus, we compute the resulting limb as follows: + // result.limb := predicate * (other.limb - this.limb) + this.limb. + // + // Note that each call to `madd` will add a gate as predicate is a witness at this point. + // There can be edge cases where `this` and `other` are both constants and only differ in one limb. + // In such a case, the `madd` for the differing limb will be a no-op (i.e., redundant gate), as the + // difference will be zero. For example, + // binary limbs prime limb + // this: (0x5, 0x1, 0x0, 0x0) (0x100000000000000005) + // other: (0x7, 0x1, 0x0, 0x0) (0x100000000000000007) + // Here, the `madd` for the second, third and fourth binary limbs will be a no-op, as the difference + // between `this` and `other` is zero for those limbs. + // + // We allow this to happen because we want to maintain limb consistency (i.e., all limbs either witness or + // constant). + field_ct binary_limb_0 = field_ct(predicate).madd( other.binary_basis_limbs[0].element - binary_basis_limbs[0].element, binary_basis_limbs[0].element); - field_t binary_limb_1 = static_cast>(predicate).madd( + field_ct binary_limb_1 = field_ct(predicate).madd( other.binary_basis_limbs[1].element - binary_basis_limbs[1].element, binary_basis_limbs[1].element); - field_t binary_limb_2 = static_cast>(predicate).madd( + field_ct binary_limb_2 = field_ct(predicate).madd( other.binary_basis_limbs[2].element - binary_basis_limbs[2].element, binary_basis_limbs[2].element); - field_t binary_limb_3 = static_cast>(predicate).madd( + field_ct binary_limb_3 = field_ct(predicate).madd( other.binary_basis_limbs[3].element - binary_basis_limbs[3].element, binary_basis_limbs[3].element); - field_t prime_limb = - static_cast>(predicate).madd(other.prime_basis_limb - prime_basis_limb, prime_basis_limb); + field_ct prime_limb = field_ct(predicate).madd(other.prime_basis_limb - prime_basis_limb, prime_basis_limb); bigfield result(ctx); // the maximum of the maximal values of elements is large enough @@ -1768,12 +1718,12 @@ template bool_t bigfield::op auto lhs = get_value() % modulus_u512; auto rhs = other.get_value() % modulus_u512; bool is_equal_raw = (lhs == rhs); - if (!ctx) { - // TODO(https://github.com/AztecProtocol/barretenberg/issues/660): null context _should_ mean that both are - // constant, but we check with an assertion to be sure. - ASSERT(is_constant() && other.is_constant()); + if (is_constant() && other.is_constant()) { return is_equal_raw; } + + // The context should not be null at this point. + ASSERT(ctx != NULL); bool_t is_equal = witness_t(ctx, is_equal_raw); // We need to manually propagate the origin tag @@ -1868,7 +1818,7 @@ template void bigfield::assert_is_in_ assert_less_than(modulus); } -template void bigfield::assert_less_than(const uint256_t upper_limit) const +template void bigfield::assert_less_than(const uint256_t& upper_limit) const { // Warning: this assumes we have run circuit construction at least once in debug mode where large non reduced // constants are NOT allowed via ASSERT @@ -2107,47 +2057,31 @@ void bigfield::unsafe_evaluate_multiply_add(const bigfield& input_le bigfield to_mul = input_to_mul; bigfield quotient = input_quotient; - // TODO(https://github.com/AztecProtocol/aztec-packages/issues/14661): what if left and to_mul both do not have a - // context? + // Either of the multiplicand must be a witness. + ASSERT(!left.is_constant() || !to_mul.is_constant()); Builder* ctx = left.context ? left.context : to_mul.context; - uint512_t max_b0 = (left.binary_basis_limbs[1].maximum_value * to_mul.binary_basis_limbs[0].maximum_value); - max_b0 += (neg_modulus_limbs_u256[1] * quotient.binary_basis_limbs[0].maximum_value); - uint512_t max_b1 = (left.binary_basis_limbs[0].maximum_value * to_mul.binary_basis_limbs[1].maximum_value); - max_b1 += (neg_modulus_limbs_u256[0] * quotient.binary_basis_limbs[1].maximum_value); - uint512_t max_c0 = (left.binary_basis_limbs[1].maximum_value * to_mul.binary_basis_limbs[1].maximum_value); - max_c0 += (neg_modulus_limbs_u256[1] * quotient.binary_basis_limbs[1].maximum_value); - uint512_t max_c1 = (left.binary_basis_limbs[2].maximum_value * to_mul.binary_basis_limbs[0].maximum_value); - max_c1 += (neg_modulus_limbs_u256[2] * quotient.binary_basis_limbs[0].maximum_value); - uint512_t max_c2 = (left.binary_basis_limbs[0].maximum_value * to_mul.binary_basis_limbs[2].maximum_value); - max_c2 += (neg_modulus_limbs_u256[0] * quotient.binary_basis_limbs[2].maximum_value); - uint512_t max_d0 = (left.binary_basis_limbs[3].maximum_value * to_mul.binary_basis_limbs[0].maximum_value); - max_d0 += (neg_modulus_limbs_u256[3] * quotient.binary_basis_limbs[0].maximum_value); - uint512_t max_d1 = (left.binary_basis_limbs[2].maximum_value * to_mul.binary_basis_limbs[1].maximum_value); - max_d1 += (neg_modulus_limbs_u256[2] * quotient.binary_basis_limbs[1].maximum_value); - uint512_t max_d2 = (left.binary_basis_limbs[1].maximum_value * to_mul.binary_basis_limbs[2].maximum_value); - max_d2 += (neg_modulus_limbs_u256[1] * quotient.binary_basis_limbs[2].maximum_value); - uint512_t max_d3 = (left.binary_basis_limbs[0].maximum_value * to_mul.binary_basis_limbs[3].maximum_value); - max_d3 += (neg_modulus_limbs_u256[0] * quotient.binary_basis_limbs[3].maximum_value); - - uint512_t max_r0 = left.binary_basis_limbs[0].maximum_value * to_mul.binary_basis_limbs[0].maximum_value; - max_r0 += (neg_modulus_limbs_u256[0] * quotient.binary_basis_limbs[0].maximum_value); - - uint512_t max_r1 = max_b0 + max_b1; - - uint256_t borrow_lo_value = 0; + // Compute the maximum value of the product of the two inputs: max(a * b) + uint512_t max_ab_lo(0); + uint512_t max_ab_hi(0); + std::tie(max_ab_lo, max_ab_hi) = compute_partial_schoolbook_multiplication(left.get_binary_basis_limb_maximums(), + to_mul.get_binary_basis_limb_maximums()); + + // Compute the maximum value of the product of the quotient and neg_modulus: max(q * p') + uint512_t max_q_neg_p_lo(0); + uint512_t max_q_neg_p_hi(0); + std::tie(max_q_neg_p_lo, max_q_neg_p_hi) = + compute_partial_schoolbook_multiplication(neg_modulus_limbs_u256, quotient.get_binary_basis_limb_maximums()); + + // Compute the maximum value that needs to be borrowed from the hi limbs to the lo limb. + // Check the README for the explanation of the borrow. + uint256_t max_remainders_lo(0); for (const auto& remainder : input_remainders) { - max_r0 += remainder.binary_basis_limbs[0].maximum_value; - max_r1 += remainder.binary_basis_limbs[1].maximum_value; - - borrow_lo_value += (remainder.binary_basis_limbs[0].maximum_value + - (remainder.binary_basis_limbs[1].maximum_value << NUM_LIMB_BITS)); + max_remainders_lo += remainder.binary_basis_limbs[0].maximum_value + + (remainder.binary_basis_limbs[1].maximum_value << NUM_LIMB_BITS); } - borrow_lo_value >>= 2 * NUM_LIMB_BITS; - field_t borrow_lo(ctx, bb::fr(borrow_lo_value)); - - const uint512_t max_r2 = max_c0 + max_c1 + max_c2; - const uint512_t max_r3 = max_d0 + max_d1 + max_d2 + max_d3; + uint256_t borrow_lo_value = max_remainders_lo >> (2 * NUM_LIMB_BITS); + field_t borrow_lo(ctx, bb::fr(borrow_lo_value)); uint512_t max_a0(0); uint512_t max_a1(0); @@ -2157,18 +2091,12 @@ void bigfield::unsafe_evaluate_multiply_add(const bigfield& input_le max_a1 += to_add[i].binary_basis_limbs[2].maximum_value + (to_add[i].binary_basis_limbs[3].maximum_value << NUM_LIMB_BITS); } - const uint512_t max_lo = max_r0 + (max_r1 << NUM_LIMB_BITS) + max_a0; + const uint512_t max_lo = max_ab_lo + max_q_neg_p_lo + max_remainders_lo + max_a0; const uint512_t max_lo_carry = max_lo >> (2 * NUM_LIMB_BITS); - const uint512_t max_hi = max_r2 + (max_r3 << NUM_LIMB_BITS) + max_a1 + max_lo_carry; + const uint512_t max_hi = max_ab_hi + max_q_neg_p_hi + max_a1 + max_lo_carry; uint64_t max_lo_bits = (max_lo.get_msb() + 1); uint64_t max_hi_bits = max_hi.get_msb() + 1; - if ((max_lo_bits & 1ULL) == 1ULL) { - ++max_lo_bits; - } - if ((max_hi_bits & 1ULL) == 1ULL) { - ++max_hi_bits; - } uint64_t carry_lo_msb = max_lo_bits - (2 * NUM_LIMB_BITS); uint64_t carry_hi_msb = max_hi_bits - (2 * NUM_LIMB_BITS); @@ -2264,7 +2192,7 @@ void bigfield::unsafe_evaluate_multiply_add(const bigfield& input_le limb_2_accumulator.emplace_back(remainders[0].binary_basis_limbs[3].element * shift_1); } - field_t remainder_limbs[4]{ + std::array, NUM_LIMBS> remainder_limbs{ field_t::accumulate(limb_0_accumulator), needs_normalize ? field_t::from_witness_index(ctx, ctx->zero_idx) : remainders[0].binary_basis_limbs[1].element, @@ -2302,8 +2230,8 @@ void bigfield::unsafe_evaluate_multiply_add(const bigfield& input_le if (carry_lo_msb <= 70 && carry_hi_msb <= 70) { ctx->range_constrain_two_limbs(hi.get_normalized_witness_index(), lo.get_normalized_witness_index(), - size_t(carry_hi_msb), - size_t(carry_lo_msb)); + static_cast(carry_hi_msb), + static_cast(carry_lo_msb)); } else { ctx->decompose_into_default_range(hi.get_normalized_witness_index(), carry_hi_msb); ctx->decompose_into_default_range(lo.get_normalized_witness_index(), carry_lo_msb); @@ -2369,26 +2297,6 @@ void bigfield::unsafe_evaluate_multiple_multiply_add(const std::vect } ASSERT(ctx != nullptr); - const auto get_product_maximum = [](const bigfield& left, const bigfield& right) { - uint512_t max_b0_inner = (left.binary_basis_limbs[1].maximum_value * right.binary_basis_limbs[0].maximum_value); - uint512_t max_b1_inner = (left.binary_basis_limbs[0].maximum_value * right.binary_basis_limbs[1].maximum_value); - uint512_t max_c0_inner = (left.binary_basis_limbs[1].maximum_value * right.binary_basis_limbs[1].maximum_value); - uint512_t max_c1_inner = (left.binary_basis_limbs[2].maximum_value * right.binary_basis_limbs[0].maximum_value); - uint512_t max_c2_inner = (left.binary_basis_limbs[0].maximum_value * right.binary_basis_limbs[2].maximum_value); - uint512_t max_d0_inner = (left.binary_basis_limbs[3].maximum_value * right.binary_basis_limbs[0].maximum_value); - uint512_t max_d1_inner = (left.binary_basis_limbs[2].maximum_value * right.binary_basis_limbs[1].maximum_value); - uint512_t max_d2_inner = (left.binary_basis_limbs[1].maximum_value * right.binary_basis_limbs[2].maximum_value); - uint512_t max_d3_inner = (left.binary_basis_limbs[0].maximum_value * right.binary_basis_limbs[3].maximum_value); - uint512_t max_r0_inner = left.binary_basis_limbs[0].maximum_value * right.binary_basis_limbs[0].maximum_value; - - const uint512_t max_r1_inner = max_b0_inner + max_b1_inner; - const uint512_t max_r2_inner = max_c0_inner + max_c1_inner + max_c2_inner; - const uint512_t max_r3_inner = max_d0_inner + max_d1_inner + max_d2_inner + max_d3_inner; - const uint512_t max_lo_temp = max_r0_inner + (max_r1_inner << NUM_LIMB_BITS); - const uint512_t max_hi_temp = max_r2_inner + (max_r3_inner << NUM_LIMB_BITS); - return std::pair(max_lo_temp, max_hi_temp); - }; - /** * Step 1: Compute the maximum potential value of our product limbs * @@ -2399,42 +2307,23 @@ void bigfield::unsafe_evaluate_multiple_multiply_add(const std::vect uint512_t max_lo = 0; uint512_t max_hi = 0; - // Compute max values of quotient product limb products - uint512_t max_b0 = (neg_modulus_limbs_u256[1] * quotient.binary_basis_limbs[0].maximum_value); - uint512_t max_b1 = (neg_modulus_limbs_u256[0] * quotient.binary_basis_limbs[1].maximum_value); - uint512_t max_c0 = (neg_modulus_limbs_u256[1] * quotient.binary_basis_limbs[1].maximum_value); - uint512_t max_c1 = (neg_modulus_limbs_u256[2] * quotient.binary_basis_limbs[0].maximum_value); - uint512_t max_c2 = (neg_modulus_limbs_u256[0] * quotient.binary_basis_limbs[2].maximum_value); - uint512_t max_d0 = (neg_modulus_limbs_u256[3] * quotient.binary_basis_limbs[0].maximum_value); - uint512_t max_d1 = (neg_modulus_limbs_u256[2] * quotient.binary_basis_limbs[1].maximum_value); - uint512_t max_d2 = (neg_modulus_limbs_u256[1] * quotient.binary_basis_limbs[2].maximum_value); - uint512_t max_d3 = (neg_modulus_limbs_u256[0] * quotient.binary_basis_limbs[3].maximum_value); - - // max_r0 = terms from 0 - 2^2t - // max_r1 = terms from 2^t - 2^3t - // max_r2 = terms from 2^2t - 2^4t - // max_r3 = terms from 2^3t - 2^5t - uint512_t max_r0 = (neg_modulus_limbs_u256[0] * quotient.binary_basis_limbs[0].maximum_value); - max_r0 += (neg_modulus_limbs_u256[0] * quotient.binary_basis_limbs[0].maximum_value); - uint512_t max_r1 = max_b0 + max_b1; - - uint256_t borrow_lo_value(0); + // Compute the maximum value that needs to be borrowed from the hi limbs to the lo limb. + // Check the README for the explanation of the borrow. + uint256_t max_remainders_lo(0); for (const auto& remainder : input_remainders) { - max_r0 += remainder.binary_basis_limbs[0].maximum_value; - max_r1 += remainder.binary_basis_limbs[1].maximum_value; - - borrow_lo_value += remainder.binary_basis_limbs[0].maximum_value + - (remainder.binary_basis_limbs[1].maximum_value << NUM_LIMB_BITS); + max_remainders_lo += remainder.binary_basis_limbs[0].maximum_value + + (remainder.binary_basis_limbs[1].maximum_value << NUM_LIMB_BITS); } - borrow_lo_value >>= 2 * NUM_LIMB_BITS; + uint256_t borrow_lo_value = max_remainders_lo >> (2 * NUM_LIMB_BITS); field_t borrow_lo(ctx, bb::fr(borrow_lo_value)); - const uint512_t max_r2 = max_c0 + max_c1 + max_c2; - const uint512_t max_r3 = max_d0 + max_d1 + max_d2 + max_d3; + // Compute the maximum value of the quotient times modulus. + const auto [max_q_neg_p_lo, max_q_neg_p_hi] = + compute_partial_schoolbook_multiplication(neg_modulus_limbs_u256, quotient.get_binary_basis_limb_maximums()); // update max_lo, max_hi with quotient limb product terms. - max_lo += max_r0 + (max_r1 << NUM_LIMB_BITS); - max_hi += max_r2 + (max_r3 << NUM_LIMB_BITS); + max_lo += max_q_neg_p_lo + max_remainders_lo; + max_hi += max_q_neg_p_hi; // Compute maximum value of addition terms in `to_add` and add to max_lo, max_hi uint512_t max_a0(0); @@ -2450,7 +2339,8 @@ void bigfield::unsafe_evaluate_multiple_multiply_add(const std::vect // Compute the maximum value of our multiplication products and add to max_lo, max_hi for (size_t i = 0; i < num_multiplications; ++i) { - const auto [product_lo, product_hi] = get_product_maximum(left[i], right[i]); + const auto [product_lo, product_hi] = compute_partial_schoolbook_multiplication( + left[i].get_binary_basis_limb_maximums(), right[i].get_binary_basis_limb_maximums()); max_lo += product_lo; max_hi += product_hi; } @@ -2461,14 +2351,6 @@ void bigfield::unsafe_evaluate_multiple_multiply_add(const std::vect // will need to apply to validate our product uint64_t max_lo_bits = (max_lo.get_msb() + 1); uint64_t max_hi_bits = max_hi.get_msb() + 1; - // Turbo range checks only work for even bit ranges, so make sure these values are even - // TODO: This neccessary anymore? Turbo range checks now work with odd bit ranges... - if ((max_lo_bits & 1ULL) == 1ULL) { - ++max_lo_bits; - } - if ((max_hi_bits & 1ULL) == 1ULL) { - ++max_hi_bits; - } // The custom bigfield multiplication gate requires inputs are witnesses. // If we're using constant values, instantiate them as circuit variables @@ -2557,7 +2439,7 @@ void bigfield::unsafe_evaluate_multiple_multiply_add(const std::vect quotient = convert_constant_to_fixed_witness(quotient); } - bool no_remainders = remainders.size() == 0; + bool no_remainders = remainders.empty(); if (!no_remainders) { limb_0_accumulator.emplace_back(remainders[0].binary_basis_limbs[0].element); limb_2_accumulator.emplace_back(remainders[0].binary_basis_limbs[2].element); @@ -2598,7 +2480,7 @@ void bigfield::unsafe_evaluate_multiple_multiply_add(const std::vect if (remainder3.is_constant()) { remainder3 = field_t::from_witness_index(ctx, ctx->put_constant_variable(remainder3.get_value())); } - field_t remainder_limbs[4]{ + std::array, NUM_LIMBS> remainder_limbs{ accumulated_lo, remainder1, accumulated_hi, @@ -2646,8 +2528,8 @@ void bigfield::unsafe_evaluate_multiple_multiply_add(const std::vect if (carry_lo_msb <= 70 && carry_hi_msb <= 70) { ctx->range_constrain_two_limbs(hi.get_normalized_witness_index(), lo.get_normalized_witness_index(), - (size_t)carry_hi_msb, - (size_t)carry_lo_msb); + static_cast(carry_hi_msb), + static_cast(carry_lo_msb)); } else { ctx->decompose_into_default_range(hi.get_normalized_witness_index(), carry_hi_msb); ctx->decompose_into_default_range(lo.get_normalized_witness_index(), carry_lo_msb); @@ -2754,4 +2636,27 @@ std::pair bigfield::get_quotient_reduction_info(const return std::pair(false, num_quotient_bits); } +template +std::pair bigfield::compute_partial_schoolbook_multiplication( + const std::array& a_limbs, const std::array& b_limbs) +{ + const uint512_t b0_inner = (a_limbs[1] * b_limbs[0]); + const uint512_t b1_inner = (a_limbs[0] * b_limbs[1]); + const uint512_t c0_inner = (a_limbs[1] * b_limbs[1]); + const uint512_t c1_inner = (a_limbs[2] * b_limbs[0]); + const uint512_t c2_inner = (a_limbs[0] * b_limbs[2]); + const uint512_t d0_inner = (a_limbs[3] * b_limbs[0]); + const uint512_t d1_inner = (a_limbs[2] * b_limbs[1]); + const uint512_t d2_inner = (a_limbs[1] * b_limbs[2]); + const uint512_t d3_inner = (a_limbs[0] * b_limbs[3]); + + const uint512_t r0_inner = (a_limbs[0] * b_limbs[0]); // c0 := a0 * b0 + const uint512_t r1_inner = b0_inner + b1_inner; // c1 := a1 * b0 + a0 * b1 + const uint512_t r2_inner = c0_inner + c1_inner + c2_inner; // c2 := a2 * b0 + a1 * b1 + a0 * b2 + const uint512_t r3_inner = d0_inner + d1_inner + d2_inner + d3_inner; // c3 := a3 * b0 + a2 * b1 + a1 * b2 + a0 * b3 + const uint512_t lo_val = r0_inner + (r1_inner << NUM_LIMB_BITS); // lo := c0 + c1 * 2^b + const uint512_t hi_val = r2_inner + (r3_inner << NUM_LIMB_BITS); // hi := c2 + c3 * 2^b + return std::pair(lo_val, hi_val); +} + } // namespace bb::stdlib