diff --git a/cpp/src/barretenberg/stdlib/primitives/field/array.hpp b/cpp/src/barretenberg/stdlib/primitives/field/array.hpp index 59f6710f39..5ed9ff32f7 100644 --- a/cpp/src/barretenberg/stdlib/primitives/field/array.hpp +++ b/cpp/src/barretenberg/stdlib/primitives/field/array.hpp @@ -1,16 +1,16 @@ #pragma once #include "field.hpp" +#include "../safe_uint/safe_uint.hpp" #include "../bool/bool.hpp" namespace plonk { namespace stdlib { /** - * Gets the number of contiguous nonzero values of an array. - * Note: this assumes `0` always means 'not used', so be careful. If you actually want `0` to be counted, you'll need - * something else. + * Gets the number of contiguous nonzero values of an array from the start. + * Note: This assumes `0` always means 'not used', so be careful. As soon as we locate 0, we stop the counting. + * If you actually want `0` to be counted, you'll need something else. */ -// TODO: move to own file of helper functions. template field_t array_length(std::array, SIZE> const& arr) { field_t length = 0; @@ -31,7 +31,7 @@ template field_t array_length(std::ar */ template field_t array_pop(std::array, SIZE> const& arr) { - field_t popped_value; + field_t popped_value = 0; bool_t already_popped = false; for (size_t i = arr.size() - 1; i != (size_t)-1; i--) { bool_t is_non_zero = arr[i] != 0; @@ -39,7 +39,7 @@ template field_t array_pop(std::array already_popped |= is_non_zero; } - already_popped.assert_equal(true, "Cannot pop from an empty array"); + already_popped.assert_equal(true, "array_pop cannot pop from an empty array"); return popped_value; }; @@ -58,7 +58,7 @@ void array_push(std::array, SIZE>& arr, field_t cons already_pushed |= is_zero; } - already_pushed.assert_equal(true, "Cannot push to a full array"); + already_pushed.assert_equal(true, "array_push cannot push to a full array"); }; template @@ -70,7 +70,7 @@ inline size_t array_push(std::array>, SIZE>& arr return i; } } - throw_or_abort("Cannot push to a full array"); + throw_or_abort("array_push cannot push to a full array"); }; template @@ -82,7 +82,7 @@ inline size_t array_push(std::array, SIZE>& arr, std::shared_ return i; } } - throw_or_abort("Cannot push to a full array"); + throw_or_abort("array_push cannot push to a full array"); }; /** @@ -111,17 +111,11 @@ void push_array_to_array(std::array, size_1> const& source, // TODO: inefficient to get length this way within this function. Probably best to inline the checks that we need // into the below loops directly. field_t target_length = array_length(target); - field_t source_length = array_length(source); field_t target_capacity = field_t(target.size()); const field_t overflow_capacity = target_capacity + 1; - // TODO: using safe_uint for an underflow check, do: - // remaining_target_capacity = target_capacity.subtract(target_length + source_length); - - ASSERT(target_capacity.get_value() + 1 > target_length.get_value() + source_length.get_value()); - - info("source: ", source); - info("target before: ", target); + // ASSERT(uint256_t(target_capacity.get_value()) + 1 > + // uint256_t(target_length.get_value()) + uint256_t(source_length.get_value())); field_t j_ct = 0; // circuit-type index for the inner loop field_t next_target_index = target_length; @@ -141,12 +135,10 @@ void push_array_to_array(std::array, size_1> const& source, next_target_index++; - next_target_index.assert_not_equal(overflow_capacity, "Target array capacity exceeded"); + next_target_index.assert_not_equal(overflow_capacity, "push_array_to_array target array capacity exceeded"); j_ct = i + 1; } - - info("target after: ", target); } } // namespace stdlib diff --git a/cpp/src/barretenberg/stdlib/primitives/field/field.test.cpp b/cpp/src/barretenberg/stdlib/primitives/field/field.test.cpp index d995f93b51..3304b12dc2 100644 --- a/cpp/src/barretenberg/stdlib/primitives/field/field.test.cpp +++ b/cpp/src/barretenberg/stdlib/primitives/field/field.test.cpp @@ -1,5 +1,6 @@ #include "../bool/bool.hpp" #include "field.hpp" +#include "array.hpp" #include "barretenberg/plonk/proof_system/constants.hpp" #include #include "barretenberg/honk/composer/standard_honk_composer.hpp" @@ -971,6 +972,269 @@ template class stdlib_field : public testing::Test { bool proof_result = verifier.verify_proof(proof); EXPECT_EQ(proof_result, true); } + + static void test_array_length() + { + Composer composer = Composer(); + + constexpr size_t ARRAY_LEN = 10; + std::array values; + std::array values_ct; + + constexpr size_t filled = 6; + for (size_t i = 0; i < filled; i++) { + values[i] = fr::random_element(); + values_ct[i] = witness_ct(&composer, values[i]); + } + auto filled_len = array_length(values_ct); + EXPECT_EQ(filled_len.get_value(), filled); + + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + auto proof = prover.construct_proof(); + info("composer gates = ", composer.get_num_gates()); + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); + } + + static void test_array_pop() + { + Composer composer = Composer(); + + constexpr size_t ARRAY_LEN = 10; + std::array values; + std::array values_ct; + + constexpr size_t filled = 6; + for (size_t i = 0; i < filled; i++) { + values[i] = fr::random_element(); + values_ct[i] = witness_ct(&composer, values[i]); + } + auto popped = array_pop(values_ct); + EXPECT_EQ(popped.get_value(), values[filled - 1]); + + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + auto proof = prover.construct_proof(); + info("composer gates = ", composer.get_num_gates()); + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); + }; + + static void test_array_pop_from_empty() + { + Composer composer = Composer(); + + constexpr size_t ARRAY_LEN = 10; + std::array values; + std::array values_ct; + + constexpr size_t filled = 0; + for (size_t i = 0; i < filled; i++) { + values[i] = fr::random_element(); + values_ct[i] = witness_ct(&composer, values[i]); + } + for (size_t i = filled; i < ARRAY_LEN; i++) { + values[i] = 0; + values_ct[i] = witness_ct(&composer, values[i]); + } + + auto popped = array_pop(values_ct); + EXPECT_EQ(popped.get_value(), 0); + + EXPECT_EQ(composer.failed(), true); + EXPECT_EQ(composer.err(), "array_pop cannot pop from an empty array"); + }; + + static void test_array_push() + { + Composer composer = Composer(); + + constexpr size_t ARRAY_LEN = 10; + std::array values; + std::array values_ct; + + constexpr size_t filled = 6; + for (size_t i = 0; i < filled; i++) { + values[i] = fr::random_element(); + values_ct[i] = witness_ct(&composer, values[i]); + } + for (size_t i = filled; i < ARRAY_LEN; i++) { + values[i] = 0; + values_ct[i] = witness_ct(&composer, values[i]); + } + + auto value_ct = field_ct(&composer, fr::random_element()); + array_push(values_ct, value_ct); + EXPECT_EQ(value_ct.get_value(), values_ct[filled].get_value()); + + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + auto proof = prover.construct_proof(); + info("composer gates = ", composer.get_num_gates()); + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); + } + + static void test_array_push_optional() + { + Composer composer = Composer(); + + constexpr size_t ARRAY_LEN = 10; + std::array, ARRAY_LEN> values; + std::array, ARRAY_LEN> values_ct; + + // Fill the array with some values + for (size_t i = 0; i < ARRAY_LEN; i++) { + values[i] = std::nullopt; + values_ct[i] = std::nullopt; + } + + // Push some values into the array + size_t num_pushes = 0; + for (size_t i = 0; i < ARRAY_LEN; i++) { + auto value = field_ct(&composer, fr::random_element()); + size_t idx = array_push(values_ct, value); + EXPECT_TRUE(values_ct[idx].has_value()); + EXPECT_EQ(values_ct[idx].value().get_value(), value.get_value()); + num_pushes++; + } + + // Make sure the array is full now + try { + auto value = field_ct(&composer, fr::random_element()); + array_push(values_ct, value); + FAIL() << "array_push should have thrown an exception when trying to push to a full array"; + } catch (std::runtime_error& e) { + EXPECT_EQ(e.what(), std::string("array_push cannot push to a full array")); + } + + EXPECT_EQ(num_pushes, ARRAY_LEN); + + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + auto proof = prover.construct_proof(); + info("composer gates = ", composer.get_num_gates()); + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); + } + + static void test_array_push_shared_ptr() + { + constexpr size_t ARRAY_LEN = 5; + std::array, ARRAY_LEN> arr; + for (size_t i = 0; i < arr.size(); ++i) { + arr[i] = nullptr; + } + + // Fill the array up to capacity + for (size_t i = 0; i < arr.size(); ++i) { + arr[i] = std::make_shared(i); + } + + // Attempt to push a value to the array + std::shared_ptr new_value = std::make_shared(42); + EXPECT_THROW(array_push(arr, new_value), std::runtime_error); + + // Ensure that the array was not modified + for (size_t i = 0; i < arr.size(); ++i) { + EXPECT_NE(arr[i], new_value); + } + } + + static void test_is_array_empty() + { + Composer composer = Composer(); + + constexpr size_t ARRAY_LEN = 10; + std::array values; + std::array values_ct; + + // Test non-empty array + constexpr size_t filled = 3; + for (size_t i = 0; i < filled; i++) { + values[i] = fr::random_element(); + values_ct[i] = witness_ct(&composer, values[i]); + } + for (size_t i = filled; i < ARRAY_LEN; i++) { + values[i] = 0; + values_ct[i] = witness_ct(&composer, values[i]); + } + auto is_empty = is_array_empty(values_ct); + EXPECT_EQ(is_empty.get_value(), false); + + // Test empty array + for (size_t i = 0; i < ARRAY_LEN; i++) { + values[i] = 0; + values_ct[i] = witness_ct(&composer, values[i]); + } + is_empty = is_array_empty(values_ct); + EXPECT_EQ(is_empty.get_value(), true); + + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + auto proof = prover.construct_proof(); + info("composer gates = ", composer.get_num_gates()); + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); + }; + + static void test_push_array_to_array() + { + Composer composer = Composer(); + + std::array source = { 1, 2 }; + std::array target = { 3, 4, 6, 8, 0, 0, 0, 0 }; + push_array_to_array(source, target); + + // Check that the source array has been inserted into the first available index of the target array. + ASSERT(target[0].get_value() == 3); + ASSERT(target[1].get_value() == 4); + ASSERT(target[2].get_value() == 6); + ASSERT(target[3].get_value() == 8); + ASSERT(target[4].get_value() == 1); + ASSERT(target[5].get_value() == 2); + ASSERT(target[6].get_value() == 0); + ASSERT(target[7].get_value() == 0); + + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + auto proof = prover.construct_proof(); + info("composer gates = ", composer.get_num_gates()); + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); + } + + static void test_push_array_to_array_full() + { + Composer composer = Composer(); + + std::array source = { 1, 2 }; + std::array target = { 3, 4, 6, 8, 7, 9, 5, 0 }; + std::array source_ct; + std::array target_ct; + for (size_t i = 0; i < source.size(); i++) { + source_ct[i] = witness_ct(&composer, source[i]); + } + for (size_t i = 0; i < target.size(); i++) { + target_ct[i] = witness_ct(&composer, target[i]); + } + + push_array_to_array(source_ct, target_ct); + + // Check that the target array is unchanged as the push operation failed + ASSERT(target_ct[0].get_value() == 3); + ASSERT(target_ct[1].get_value() == 4); + ASSERT(target_ct[2].get_value() == 6); + ASSERT(target_ct[3].get_value() == 8); + ASSERT(target_ct[4].get_value() == 7); + ASSERT(target_ct[5].get_value() == 9); + ASSERT(target_ct[6].get_value() == 5); + ASSERT(target_ct[7].get_value() == 0); + + EXPECT_EQ(composer.failed(), true); + EXPECT_EQ(composer.err(), "push_array_to_array target array capacity exceeded"); + } }; typedef testing::Types @@ -1098,4 +1362,32 @@ TYPED_TEST(stdlib_field, test_copy_as_new_witness) { TestFixture::test_copy_as_new_witness(); } +TYPED_TEST(stdlib_field, test_array_len) +{ + TestFixture::test_array_length(); +} +TYPED_TEST(stdlib_field, test_array_pop) +{ + TestFixture::test_array_pop(); +} +TYPED_TEST(stdlib_field, test_array_pop_from_empty) +{ + TestFixture::test_array_pop_from_empty(); +} +TYPED_TEST(stdlib_field, test_array_push) +{ + TestFixture::test_array_push(); +} +TYPED_TEST(stdlib_field, test_array_push_optional) +{ + TestFixture::test_array_push_optional(); +} +TYPED_TEST(stdlib_field, test_array_push_array_to_array) +{ + TestFixture::test_push_array_to_array(); +} +TYPED_TEST(stdlib_field, test_array_push_array_to_array_full) +{ + TestFixture::test_push_array_to_array_full(); +} } // namespace test_stdlib_field