From f99ab9619bf4fe80274e522a509d67f5d6843d4d Mon Sep 17 00:00:00 2001 From: Maddiaa <47148561+cheethas@users.noreply.github.com> Date: Mon, 3 Apr 2023 07:47:54 -0700 Subject: [PATCH 1/4] feat(memory_tree|a3): add sibling path calculations (#301) * feat(memory_tree): frontier paths * fix: sibling path rename, nullifier tree -> protected vars * clean * test: sibling path test --------- Co-authored-by: cheethas --- .../stdlib/merkle_tree/hash_path.hpp | 1 + .../stdlib/merkle_tree/memory_tree.cpp | 18 +++++++++ .../stdlib/merkle_tree/memory_tree.hpp | 2 + .../stdlib/merkle_tree/memory_tree.test.cpp | 37 ++++++++++++++++++- .../nullifier_tree/nullifier_leaf.hpp | 3 +- .../nullifier_tree/nullifier_memory_tree.hpp | 3 +- 6 files changed, 61 insertions(+), 3 deletions(-) diff --git a/cpp/src/barretenberg/stdlib/merkle_tree/hash_path.hpp b/cpp/src/barretenberg/stdlib/merkle_tree/hash_path.hpp index de8276bb1e..a157e2e68b 100644 --- a/cpp/src/barretenberg/stdlib/merkle_tree/hash_path.hpp +++ b/cpp/src/barretenberg/stdlib/merkle_tree/hash_path.hpp @@ -11,6 +11,7 @@ namespace merkle_tree { using namespace barretenberg; typedef std::vector> fr_hash_path; +typedef std::vector fr_sibling_path; template using hash_path = std::vector, field_t>>; inline fr_hash_path get_new_hash_path(fr_hash_path const& old_path, uint128_t index, fr const& value) diff --git a/cpp/src/barretenberg/stdlib/merkle_tree/memory_tree.cpp b/cpp/src/barretenberg/stdlib/merkle_tree/memory_tree.cpp index 685ee73aa2..aa716503b2 100644 --- a/cpp/src/barretenberg/stdlib/merkle_tree/memory_tree.cpp +++ b/cpp/src/barretenberg/stdlib/merkle_tree/memory_tree.cpp @@ -40,6 +40,24 @@ fr_hash_path MemoryTree::get_hash_path(size_t index) return path; } +fr_sibling_path MemoryTree::get_sibling_path(size_t index) +{ + fr_sibling_path path(depth_); + size_t offset = 0; + size_t layer_size = total_size_; + for (size_t i = 0; i < depth_; i++) { + if (index % 2 == 0) { + path[i] = hashes_[offset + index + 1]; + } else { + path[i] = hashes_[offset + index - 1]; + } + offset += layer_size; + layer_size >>= 1; + index >>= 1; + } + return path; +} + fr MemoryTree::update_element(size_t index, fr const& value) { size_t offset = 0; diff --git a/cpp/src/barretenberg/stdlib/merkle_tree/memory_tree.hpp b/cpp/src/barretenberg/stdlib/merkle_tree/memory_tree.hpp index ccec978120..1e2e1480dc 100644 --- a/cpp/src/barretenberg/stdlib/merkle_tree/memory_tree.hpp +++ b/cpp/src/barretenberg/stdlib/merkle_tree/memory_tree.hpp @@ -30,6 +30,8 @@ class MemoryTree { fr_hash_path get_hash_path(size_t index); + fr_sibling_path get_sibling_path(size_t index); + fr update_element(size_t index, fr const& value); fr root() const { return root_; } diff --git a/cpp/src/barretenberg/stdlib/merkle_tree/memory_tree.test.cpp b/cpp/src/barretenberg/stdlib/merkle_tree/memory_tree.test.cpp index 7466eeb15b..6996c90f40 100644 --- a/cpp/src/barretenberg/stdlib/merkle_tree/memory_tree.test.cpp +++ b/cpp/src/barretenberg/stdlib/merkle_tree/memory_tree.test.cpp @@ -1,6 +1,5 @@ #include "memory_tree.hpp" #include -#include "barretenberg/stdlib/types/types.hpp" using namespace barretenberg; using namespace plonk::stdlib::merkle_tree; @@ -43,3 +42,39 @@ TEST(stdlib_merkle_tree, test_memory_store) EXPECT_EQ(db.get_hash_path(3), expected); EXPECT_EQ(db.root(), root); } + +TEST(stdlib_merkle_tree, test_memory_store_sibling_path) +{ + fr e00 = 0; + fr e01 = VALUES[1]; + fr e02 = VALUES[2]; + fr e03 = VALUES[3]; + fr e10 = hash_pair_native(e00, e01); + fr e11 = hash_pair_native(e02, e03); + fr root = hash_pair_native(e10, e11); + + MemoryTree db(2); + for (size_t i = 0; i < 4; ++i) { + db.update_element(i, VALUES[i]); + } + + // Check correct paths are generated for each layer 0 element + fr_sibling_path expected00 = { + e01, + e11, + }; + fr_sibling_path expected01 = { e00, e11 }; + fr_sibling_path expected02 = { + e03, + e10, + }; + fr_sibling_path expected03 = { + e02, + e10, + }; + EXPECT_EQ(db.get_sibling_path(0), expected00); + EXPECT_EQ(db.get_sibling_path(1), expected01); + EXPECT_EQ(db.get_sibling_path(2), expected02); + EXPECT_EQ(db.get_sibling_path(3), expected03); + EXPECT_EQ(db.root(), root); +} diff --git a/cpp/src/barretenberg/stdlib/merkle_tree/nullifier_tree/nullifier_leaf.hpp b/cpp/src/barretenberg/stdlib/merkle_tree/nullifier_tree/nullifier_leaf.hpp index d94c6638a0..03a6a25d91 100644 --- a/cpp/src/barretenberg/stdlib/merkle_tree/nullifier_tree/nullifier_leaf.hpp +++ b/cpp/src/barretenberg/stdlib/merkle_tree/nullifier_tree/nullifier_leaf.hpp @@ -44,9 +44,10 @@ inline std::pair find_closest_leaf(std::vector con { std::vector diff; bool repeated = false; + auto new_value_ = uint256_t(new_value); + for (size_t i = 0; i < leaves_.size(); i++) { auto leaf_value_ = uint256_t(leaves_[i].value); - auto new_value_ = uint256_t(new_value); if (leaf_value_ > new_value_) { diff.push_back(leaf_value_); } else if (leaf_value_ == new_value_) { diff --git a/cpp/src/barretenberg/stdlib/merkle_tree/nullifier_tree/nullifier_memory_tree.hpp b/cpp/src/barretenberg/stdlib/merkle_tree/nullifier_tree/nullifier_memory_tree.hpp index 3b49fc19bd..6fc273675b 100644 --- a/cpp/src/barretenberg/stdlib/merkle_tree/nullifier_tree/nullifier_memory_tree.hpp +++ b/cpp/src/barretenberg/stdlib/merkle_tree/nullifier_tree/nullifier_memory_tree.hpp @@ -79,8 +79,9 @@ class NullifierMemoryTree : public MemoryTree { const std::vector& get_hashes() { return hashes_; } const std::vector& get_leaves() { return leaves_; } + const nullifier_leaf& get_leaf(size_t index) { return leaves_[index]; } - private: + protected: using MemoryTree::depth_; using MemoryTree::hashes_; using MemoryTree::root_; From 08e02b69239dd6c9a6d73ba1839b8e566697563f Mon Sep 17 00:00:00 2001 From: Michael Connor Date: Tue, 4 Apr 2023 01:10:59 +0100 Subject: [PATCH 2/4] fix array and resolve merge conflicts (#305) * fix array and resolve merge conflicts * Remove suyash's function (its less efficient). --------- Co-authored-by: Suyash Bagad --- .../stdlib/primitives/bool/bool.test.cpp | 2 + .../stdlib/primitives/field/array.hpp | 72 +- .../stdlib/primitives/field/array.test.cpp | 635 ++++++++++++++++++ .../stdlib/primitives/field/field.test.cpp | 445 ------------ 4 files changed, 674 insertions(+), 480 deletions(-) create mode 100644 cpp/src/barretenberg/stdlib/primitives/field/array.test.cpp diff --git a/cpp/src/barretenberg/stdlib/primitives/bool/bool.test.cpp b/cpp/src/barretenberg/stdlib/primitives/bool/bool.test.cpp index 3e27bd9891..a9e99d496e 100644 --- a/cpp/src/barretenberg/stdlib/primitives/bool/bool.test.cpp +++ b/cpp/src/barretenberg/stdlib/primitives/bool/bool.test.cpp @@ -391,6 +391,8 @@ TEST(stdlib_bool, must_imply) EXPECT_EQ(result, true); } +// TODO: must_imply failure case + TEST(stdlib_bool, must_imply_multiple) { honk::StandardHonkComposer composer = honk::StandardHonkComposer(); diff --git a/cpp/src/barretenberg/stdlib/primitives/field/array.hpp b/cpp/src/barretenberg/stdlib/primitives/field/array.hpp index a75dd17526..93ce1f1eee 100644 --- a/cpp/src/barretenberg/stdlib/primitives/field/array.hpp +++ b/cpp/src/barretenberg/stdlib/primitives/field/array.hpp @@ -16,7 +16,9 @@ template field_t array_length(std::ar field_t length = 0; bool_t hit_zero = false; for (const auto& e : arr) { - hit_zero |= e == 0; + bool_t is_zero = e.is_zero(); + hit_zero.must_imply(is_zero, "Once we've hit the first zero, there must only be zeros thereafter!"); + hit_zero |= is_zero; const field_t increment = !hit_zero; length += increment; } @@ -132,44 +134,44 @@ template void push_array_to_array(std::array, size_1> const& source, std::array, size_2>& target) { - ASSERT(target.size() >= source.size()); - field_t source_length = array_length(source); field_t target_length = array_length(target); + const field_t overflow_capacity = target.max_size() + 1; + + field_t j_ct = 0; // circuit-type index for the inner loop + // Find the first empty slot in the target: + field_t next_target_index = target_length; + + bool_t hit_s_zero = false; + bool_t not_hit_s_zero = true; + + for (size_t i = 0; i < source.max_size(); ++i) { + // Loop over each source value we want to push: + auto& s = source[i]; + { + auto is_s_zero = s.is_zero(); + hit_s_zero.must_imply(is_s_zero, + "Once we've hit the first source zero, there must only be zeros thereafter!"); + hit_s_zero |= is_s_zero; + not_hit_s_zero = !hit_s_zero; + } - // Check if the `source` array is too large vs the remaining capacity of the `target` array - auto source_length_safe = safe_uint_t(source_length, sizeof(size_t) * 8, "source_array_len"); - auto target_length_safe = safe_uint_t(target_length, sizeof(size_t) * 8, "target_array_len"); - auto target_capacity_safe = - safe_uint_t(field_t(target.size()), sizeof(size_t) * 8, "target_array_capacity"); - target_capacity_safe.subtract((source_length_safe + target_length_safe), - sizeof(size_t) * 8, - "push_array_to_array target array capacity exceeded"); - - // Ensure that all the elements after the first zero-element in target are zero - field_t num_non_zero_in_target = 0; - for (size_t i = 0; i < size_2; i++) { - num_non_zero_in_target += !target[i].is_zero(); - } - num_non_zero_in_target.assert_equal(target_length_safe, "non-zero element in target array after a zero element"); + // Triangular loop: + for (size_t j = i; j < target.max_size(); ++j) { + auto& t = target[j]; - // Ensure that all the elements after the first zero-element in source are zero - field_t num_non_zero_in_source = 0; - for (size_t i = 0; i < size_1; i++) { - num_non_zero_in_source += !source[i].is_zero(); - } - num_non_zero_in_source.assert_equal(source_length_safe, "non-zero element in source array after a zero element"); - - // Finally, insert only non-zero elements from source into target - bool_t composer_error_found = !source_length.get_context()->err().empty(); - for (size_t i = 0; i < source.size(); i++) { - bool_t found = false; - - for (size_t j = i; j < target.size(); j++) { - bool_t is_target_non_zero = !target[j].is_zero(); - target[j] = field_t::conditional_assign( - is_target_non_zero, target[j], source[i] * !found * !composer_error_found); - found |= !is_target_non_zero; + // Check whether we've reached the next target index at which we can push `s`: + bool_t at_next_target_index = j_ct == next_target_index; + + t = field_t::conditional_assign(at_next_target_index && not_hit_s_zero, s, t); + + j_ct++; } + + next_target_index += not_hit_s_zero; + + next_target_index.assert_not_equal(overflow_capacity, "push_array_to_array target array capacity exceeded"); + + j_ct = i + 1; } } diff --git a/cpp/src/barretenberg/stdlib/primitives/field/array.test.cpp b/cpp/src/barretenberg/stdlib/primitives/field/array.test.cpp new file mode 100644 index 0000000000..cace1e5b56 --- /dev/null +++ b/cpp/src/barretenberg/stdlib/primitives/field/array.test.cpp @@ -0,0 +1,635 @@ +#include "../bool/bool.hpp" +#include "field.hpp" +#include "array.hpp" +#include +#include +#include "barretenberg/honk/composer/standard_honk_composer.hpp" +#include "barretenberg/plonk/composer/standard_composer.hpp" +#include "barretenberg/plonk/composer/ultra_composer.hpp" +#include "barretenberg/plonk/composer/turbo_composer.hpp" +#include "barretenberg/numeric/random/engine.hpp" + +using namespace bonk; + +namespace test_stdlib_array { + +namespace { +auto& engine = numeric::random::get_debug_engine(); +} + +template void ignore_unused(T&) {} // use to ignore unused variables in lambdas + +using namespace barretenberg; +using namespace plonk; + +template class stdlib_array : public testing::Test { + typedef stdlib::bool_t bool_ct; + typedef stdlib::field_t field_ct; + typedef stdlib::witness_t witness_ct; + typedef stdlib::public_witness_t public_witness_ct; + + public: + // TODO: empty array and singleton array edge cases for all array functions. + + 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_length_fails() + { + 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]); + } + + // Put a zero in the middle of the array, so that the array_length function complains that all zeros thereafter + // should be zero. + values_ct[4] = 0; + + array_length(values_ct); + + EXPECT_EQ(composer.failed(), true); + EXPECT_EQ(composer.err(), "Once we've hit the first zero, there must only be zeros thereafter!"); + } + + 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); + }; + + template + static auto test_push_array_to_array_helper(Composer& composer, + std::array const& source, + std::array const& target, + std::array const& expected_target, + bool const expect_fail = false) + { + 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 source array has been inserted into the first available index of the target array. + if (!expect_fail) { + for (size_t i = 0; i < target.size(); i++) { + EXPECT_EQ(target_ct[i].get_value(), expected_target[i]); + } + } + + bool proof_result = false; + if (composer.err().empty()) { + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + auto proof = prover.construct_proof(); + info("composer gates = ", composer.get_num_gates()); + proof_result = verifier.verify_proof(proof); + } + + return std::make_pair(proof_result, composer.err()); + } + + static void test_pata_large_bench() + { + // Benchmark + Composer composer = Composer(); + + std::array source; + std::array target = { 0 }; + std::array expected_target; + for (size_t i = 0; i < source.max_size(); ++i) { + source[i] = i + 1; + target[i] = i + 1; + expected_target[i] = i + 1; + expected_target[i + source.max_size()] = i + 1; + }; + bool proof_result; + std::string error; + std::tie(proof_result, error) = test_push_array_to_array_helper(composer, source, target, expected_target); + + EXPECT_TRUE(proof_result); + } + + static void test_pata_same_size_not_full_to_not_full() + { + Composer composer = Composer(); + + std::array source = { 1, 0, 0, 0 }; + std::array target = { 3, 0, 0, 0 }; + std::array expected_target = { 3, 1, 0, 0 }; + bool proof_result; + std::string error; + std::tie(proof_result, error) = test_push_array_to_array_helper(composer, source, target, expected_target); + + EXPECT_TRUE(proof_result); + } + + static void test_pata_same_size_not_full_to_not_full_2() + { + Composer composer = Composer(); + + std::array source = { 3, 4, 0, 0 }; + std::array target = { 1, 2, 0, 0 }; + std::array expected_target = { 1, 2, 3, 4 }; + bool proof_result; + std::string error; + std::tie(proof_result, error) = test_push_array_to_array_helper(composer, source, target, expected_target); + + EXPECT_TRUE(proof_result); + } + + static void test_pata_same_size_not_full_to_empty() + { + Composer composer = Composer(); + + std::array source = { 1, 2, 3, 0 }; + std::array target = { 0, 0, 0, 0 }; + std::array expected_target = { 1, 2, 3, 0 }; + bool proof_result; + std::string error; + std::tie(proof_result, error) = test_push_array_to_array_helper(composer, source, target, expected_target); + + EXPECT_TRUE(proof_result); + } + + static void test_pata_smaller_source_full_to_not_full() + { + Composer composer = Composer(); + + std::array source = { 1, 2, 3 }; + std::array target = { 4, 5, 6, 0, 0, 0 }; + std::array expected_target = { 4, 5, 6, 1, 2, 3 }; + bool proof_result; + std::string error; + std::tie(proof_result, error) = test_push_array_to_array_helper(composer, source, target, expected_target); + + EXPECT_TRUE(proof_result); + } + + static void test_pata_singletons_full_to_not_full() + { + Composer composer = Composer(); + + std::array source = { 1 }; + std::array target = { 0 }; + std::array expected_target = { 1 }; + bool proof_result; + std::string error; + std::tie(proof_result, error) = test_push_array_to_array_helper(composer, source, target, expected_target); + + EXPECT_TRUE(proof_result); + } + + static void test_pata_same_size_full_to_full_fails() + { + Composer composer = Composer(); + + std::array source = { 1, 2, 3, 4, 5 }; + std::array target = { 5, 6, 7, 8, 9 }; + std::array expected_target = { 5, 6, 7, 8, 9 }; + bool proof_result; + std::string error; + bool expect_fail = true; + std::tie(proof_result, error) = + test_push_array_to_array_helper(composer, source, target, expected_target, expect_fail); + + EXPECT_FALSE(proof_result); + EXPECT_EQ(error, "push_array_to_array target array capacity exceeded"); + } + + static void test_pata_nonzero_after_zero_source_fails() + { + Composer composer = Composer(); + + std::array source = { 1, 0, 2, 3 }; + std::array target = { 4, 5, 6, 7, 8, 0 }; + std::array expected_target = { 4, 5, 6, 7, 8, 0 }; + bool proof_result; + std::string error; + bool expect_fail = true; + std::tie(proof_result, error) = + test_push_array_to_array_helper(composer, source, target, expected_target, expect_fail); + + EXPECT_FALSE(proof_result); + EXPECT_EQ(error, "Once we've hit the first source zero, there must only be zeros thereafter!"); + } + + static void test_pata_nonzero_after_zero_source_fails_2() + { + Composer composer = Composer(); + + std::array source = { 1, 0, 3 }; + std::array target = { 4, 5, 2, 0, 0, 0 }; + std::array expected_target = { 4, 5, 2, 1, 0, 3 }; + bool proof_result; + std::string error; + bool expect_fail = true; + std::tie(proof_result, error) = + test_push_array_to_array_helper(composer, source, target, expected_target, expect_fail); + + EXPECT_FALSE(proof_result); + EXPECT_EQ(error, "Once we've hit the first source zero, there must only be zeros thereafter!"); + } + + static void test_pata_nonzero_after_zero_target_fails() + { + Composer composer = Composer(); + + std::array source = { 1, 2, 3 }; + std::array target = { 4, 5, 0, 6, 7, 8 }; + std::array expected_target = { 4, 5, 0, 6, 7, 8 }; + bool proof_result; + std::string error; + bool expect_fail = true; + std::tie(proof_result, error) = + test_push_array_to_array_helper(composer, source, target, expected_target, expect_fail); + + EXPECT_FALSE(proof_result); + EXPECT_EQ(error, "Once we've hit the first zero, there must only be zeros thereafter!"); + } + + static void test_pata_nonzero_after_zero_target_fails_2() + { + Composer composer = Composer(); + + std::array source = { 1, 0, 3 }; + std::array target = { 4, 5, 0, 6, 7, 8 }; + std::array expected_target = { 4, 5, 0, 6, 7, 8 }; + bool proof_result; + std::string error; + bool expect_fail = true; + std::tie(proof_result, error) = + test_push_array_to_array_helper(composer, source, target, expected_target, expect_fail); + + EXPECT_FALSE(proof_result); + EXPECT_EQ(error, "Once we've hit the first zero, there must only be zeros thereafter!"); + } + + class MockClass { + public: + MockClass() + : m_a(field_ct(0)) + , m_b(field_ct(0)) + {} + MockClass(field_ct a, field_ct b) + : m_a(a) + , m_b(b) + {} + + bool_ct is_empty() const { return m_a == 0 && m_b == 0; } + + std::pair get_values() { return std::make_pair(m_a, m_b); } + + void conditional_select(bool_ct const& condition, MockClass const& other) + { + m_a = field_ct::conditional_assign(condition, other.m_a, m_a); + m_b = field_ct::conditional_assign(condition, other.m_b, m_b); + } + + private: + field_ct m_a; + field_ct m_b; + }; + + void test_array_push_generic() + { + Composer composer = Composer(); + + constexpr size_t SIZE = 5; + std::array arr{}; + + // Push values into the array + plonk::stdlib::array_push(arr, MockClass(witness_ct(&composer, 1), witness_ct(&composer, 10))); + plonk::stdlib::array_push(arr, MockClass(witness_ct(&composer, 2), witness_ct(&composer, 20))); + plonk::stdlib::array_push(arr, MockClass(witness_ct(&composer, 3), witness_ct(&composer, 30))); + + // Check the values in the array + EXPECT_EQ(arr[0].get_values().first.get_value(), 1); + EXPECT_EQ(arr[0].get_values().second.get_value(), 10); + EXPECT_EQ(arr[1].get_values().first.get_value(), 2); + EXPECT_EQ(arr[1].get_values().second.get_value(), 20); + EXPECT_EQ(arr[2].get_values().first.get_value(), 3); + EXPECT_EQ(arr[2].get_values().second.get_value(), 30); + + 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); + } + + void test_array_push_generic_full() + { + Composer composer = Composer(); + + constexpr size_t SIZE = 5; + std::array arr{}; + + // Push values into the array + plonk::stdlib::array_push(arr, MockClass(witness_ct(&composer, 1), witness_ct(&composer, 10))); + plonk::stdlib::array_push(arr, MockClass(witness_ct(&composer, 2), witness_ct(&composer, 20))); + plonk::stdlib::array_push(arr, MockClass(witness_ct(&composer, 3), witness_ct(&composer, 30))); + plonk::stdlib::array_push(arr, MockClass(witness_ct(&composer, 4), witness_ct(&composer, 40))); + plonk::stdlib::array_push(arr, MockClass(witness_ct(&composer, 5), witness_ct(&composer, 50))); + + // Try to push into a full array + plonk::stdlib::array_push(arr, MockClass(witness_ct(&composer, 6), witness_ct(&composer, 60))); + + EXPECT_EQ(composer.failed(), true); + EXPECT_EQ(composer.err(), "array_push cannot push to a full array"); + } +}; + +typedef testing::Types + ComposerTypes; + +TYPED_TEST_SUITE(stdlib_array, ComposerTypes); + +TYPED_TEST(stdlib_array, test_array_length) +{ + TestFixture::test_array_length(); +} +TYPED_TEST(stdlib_array, test_array_length_fails) +{ + TestFixture::test_array_length_fails(); +} +TYPED_TEST(stdlib_array, test_array_pop) +{ + TestFixture::test_array_pop(); +} +TYPED_TEST(stdlib_array, test_array_pop_from_empty) +{ + TestFixture::test_array_pop_from_empty(); +} +TYPED_TEST(stdlib_array, test_array_push) +{ + TestFixture::test_array_push(); +} +TYPED_TEST(stdlib_array, test_array_push_optional) +{ + TestFixture::test_array_push_optional(); +} +TYPED_TEST(stdlib_array, test_array_push_generic) +{ + TestFixture::test_array_push_generic(); +} +TYPED_TEST(stdlib_array, test_array_push_generic_full) +{ + TestFixture::test_array_push_generic_full(); +} +// push array to array (pata) tests +TYPED_TEST(stdlib_array, test_pata_large_bench) +{ + TestFixture::test_pata_large_bench(); +} +TYPED_TEST(stdlib_array, test_pata_same_size_not_full_to_not_full) +{ + TestFixture::test_pata_same_size_not_full_to_not_full(); +} +TYPED_TEST(stdlib_array, test_pata_same_size_not_full_to_not_full_2) +{ + TestFixture::test_pata_same_size_not_full_to_not_full_2(); +} +TYPED_TEST(stdlib_array, test_pata_same_size_not_full_to_empty) +{ + TestFixture::test_pata_same_size_not_full_to_empty(); +} +TYPED_TEST(stdlib_array, test_pata_smaller_source_full_to_not_full) +{ + TestFixture::test_pata_smaller_source_full_to_not_full(); +} +TYPED_TEST(stdlib_array, test_pata_singletons_full_to_not_full) +{ + TestFixture::test_pata_singletons_full_to_not_full(); +} +TYPED_TEST(stdlib_array, test_pata_same_size_full_to_full_fails) +{ + TestFixture::test_pata_same_size_full_to_full_fails(); +} +TYPED_TEST(stdlib_array, test_pata_nonzero_after_zero_source_fails) +{ + TestFixture::test_pata_nonzero_after_zero_source_fails(); +} +TYPED_TEST(stdlib_array, test_pata_nonzero_after_zero_source_fails_2) +{ + TestFixture::test_pata_nonzero_after_zero_source_fails_2(); +} +TYPED_TEST(stdlib_array, test_pata_nonzero_after_zero_target_fails) +{ + TestFixture::test_pata_nonzero_after_zero_target_fails(); +} +TYPED_TEST(stdlib_array, test_pata_nonzero_after_zero_target_fails_2) +{ + TestFixture::test_pata_nonzero_after_zero_target_fails_2(); +} +} // namespace test_stdlib_array \ No newline at end of file diff --git a/cpp/src/barretenberg/stdlib/primitives/field/field.test.cpp b/cpp/src/barretenberg/stdlib/primitives/field/field.test.cpp index 0fa96fdc55..fae2a2bc97 100644 --- a/cpp/src/barretenberg/stdlib/primitives/field/field.test.cpp +++ b/cpp/src/barretenberg/stdlib/primitives/field/field.test.cpp @@ -974,419 +974,6 @@ 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); - }; - - template - static auto test_push_array_to_array_helper(Composer& composer, - std::array const& source, - std::array const& target, - std::array const& expected_target) - { - 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 source array has been inserted into the first available index of the target array. - for (size_t i = 0; i < target.size(); i++) { - ASSERT(target_ct[i].get_value() == expected_target[i]); - } - - bool proof_result = false; - if (composer.err().empty()) { - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - auto proof = prover.construct_proof(); - info("composer gates = ", composer.get_num_gates()); - proof_result = verifier.verify_proof(proof); - } - - return std::make_pair(proof_result, composer.err()); - } - - static void test_push_array_to_array() - { - { - Composer composer = Composer(); - - std::array source = { 1, 0, 0, 0 }; - std::array target = { 3, 0, 0, 0 }; - std::array expected_target = { 3, 1, 0, 0 }; - bool proof_res; - std::string error; - std::tie(proof_res, error) = - test_push_array_to_array_helper<4, 4>(composer, source, target, expected_target); - - EXPECT_EQ(proof_res, true); - } - { - Composer composer = Composer(); - - std::array source = { 1, 2, 3, 0 }; - std::array target = { 0, 0, 0, 0 }; - std::array expected_target = { 1, 2, 3, 0 }; - bool proof_res; - std::string error; - std::tie(proof_res, error) = - test_push_array_to_array_helper<4, 4>(composer, source, target, expected_target); - - EXPECT_EQ(proof_res, true); - } - { - Composer composer = Composer(); - - std::array source = { 1, 2, 3 }; - std::array target = { 4, 5, 6, 0, 0, 0 }; - std::array expected_target = { 4, 5, 6, 1, 2, 3 }; - bool proof_res; - std::string error; - std::tie(proof_res, error) = - test_push_array_to_array_helper<3, 6>(composer, source, target, expected_target); - - EXPECT_EQ(proof_res, true); - } - { - Composer composer = Composer(); - - std::array source = { 1, 2, 3, 4, 5 }; - std::array target = { 5, 6, 7, 8, 9 }; - std::array expected_target = { 5, 6, 7, 8, 9 }; - bool proof_res; - std::string error; - std::tie(proof_res, error) = test_push_array_to_array_helper(composer, source, target, expected_target); - - EXPECT_EQ(proof_res, false); - EXPECT_EQ( - error, - "safe_uint_t range constraint failure: subtract: push_array_to_array target array capacity exceeded"); - } - { - Composer composer = Composer(); - - std::array source = { 1, 0, 2, 3 }; - std::array target = { 4, 5, 6, 7, 8, 0 }; - std::array expected_target = { 4, 5, 6, 7, 8, 0 }; - bool proof_res; - std::string error; - std::tie(proof_res, error) = test_push_array_to_array_helper(composer, source, target, expected_target); - - EXPECT_EQ(proof_res, false); - EXPECT_EQ(error, "non-zero element in source array after a zero element"); - } - { - Composer composer = Composer(); - - std::array source = { 1, 2, 3 }; - std::array target = { 4, 5, 0, 6, 7, 8 }; - std::array expected_target = { 4, 5, 0, 6, 7, 8 }; - bool proof_res; - std::string error; - std::tie(proof_res, error) = test_push_array_to_array_helper(composer, source, target, expected_target); - - EXPECT_EQ(proof_res, false); - EXPECT_EQ(error, "non-zero element in target array after a zero element"); - } - { - // captures the last composer error - Composer composer = Composer(); - - std::array source = { 1, 0, 3 }; - std::array target = { 4, 5, 0, 6, 7, 8 }; - std::array expected_target = { 4, 5, 0, 6, 7, 8 }; - bool proof_res; - std::string error; - std::tie(proof_res, error) = test_push_array_to_array_helper(composer, source, target, expected_target); - - EXPECT_EQ(proof_res, false); - EXPECT_EQ(error, "non-zero element in target array after a zero element"); - } - } - - class MockClass { - public: - MockClass() - : m_a(field_ct(0)) - , m_b(field_ct(0)) - {} - MockClass(field_ct a, field_ct b) - : m_a(a) - , m_b(b) - {} - - bool_ct is_empty() const { return m_a == 0 && m_b == 0; } - - std::pair get_values() { return std::make_pair(m_a, m_b); } - - void conditional_select(bool_ct const& condition, MockClass const& other) - { - m_a = field_ct::conditional_assign(condition, other.m_a, m_a); - m_b = field_ct::conditional_assign(condition, other.m_b, m_b); - } - - private: - field_ct m_a; - field_ct m_b; - }; - - void test_array_push_generic() - { - Composer composer = Composer(); - - constexpr size_t SIZE = 5; - std::array arr{}; - - // Push values into the array - plonk::stdlib::array_push(arr, MockClass(witness_ct(&composer, 1), witness_ct(&composer, 10))); - plonk::stdlib::array_push(arr, MockClass(witness_ct(&composer, 2), witness_ct(&composer, 20))); - plonk::stdlib::array_push(arr, MockClass(witness_ct(&composer, 3), witness_ct(&composer, 30))); - - // Check the values in the array - EXPECT_EQ(arr[0].get_values().first.get_value(), 1); - EXPECT_EQ(arr[0].get_values().second.get_value(), 10); - EXPECT_EQ(arr[1].get_values().first.get_value(), 2); - EXPECT_EQ(arr[1].get_values().second.get_value(), 20); - EXPECT_EQ(arr[2].get_values().first.get_value(), 3); - EXPECT_EQ(arr[2].get_values().second.get_value(), 30); - - 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); - } - - void test_array_push_generic_full() - { - Composer composer = Composer(); - - constexpr size_t SIZE = 5; - std::array arr{}; - - // Push values into the array - plonk::stdlib::array_push(arr, MockClass(witness_ct(&composer, 1), witness_ct(&composer, 10))); - plonk::stdlib::array_push(arr, MockClass(witness_ct(&composer, 2), witness_ct(&composer, 20))); - plonk::stdlib::array_push(arr, MockClass(witness_ct(&composer, 3), witness_ct(&composer, 30))); - plonk::stdlib::array_push(arr, MockClass(witness_ct(&composer, 4), witness_ct(&composer, 40))); - plonk::stdlib::array_push(arr, MockClass(witness_ct(&composer, 5), witness_ct(&composer, 50))); - - // Try to push into a full array - plonk::stdlib::array_push(arr, MockClass(witness_ct(&composer, 6), witness_ct(&composer, 60))); - - EXPECT_EQ(composer.failed(), true); - EXPECT_EQ(composer.err(), "array_push cannot push to a full array"); - } }; typedef testing::Types @@ -1514,36 +1101,4 @@ 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_generic) -{ - TestFixture::test_array_push_generic(); -} -TYPED_TEST(stdlib_field, test_array_push_generic_full) -{ - TestFixture::test_array_push_generic_full(); -} -TYPED_TEST(stdlib_field, test_array_push_array_to_array) -{ - TestFixture::test_push_array_to_array(); -} } // namespace test_stdlib_field From 5b2dca0d11dea6e947eeea22d3689a905426eea3 Mon Sep 17 00:00:00 2001 From: Michael Connor Date: Tue, 4 Apr 2023 02:20:36 +0100 Subject: [PATCH 3/4] Mc/hash vk (#306) * align native and circuit vk hashing methods * fix: pass hash_index to circuit vk function * add some of the composer_type/plookup toggling for vk compress. Add some vk/eval-domain comments * rename key_witnesses->preimage_data and composer type compression to circuit-compress of recursion vk * some native verification_key_data tests * add native and recursive vk compress test. * change test name. --------- Co-authored-by: dbanks12 Co-authored-by: Suyash Bagad --- .../verification_key/verification_key.cpp | 75 ++++++++- .../verification_key/verification_key.hpp | 13 +- .../verification_key.test.cpp | 151 +++++++++++++++--- .../stdlib/primitives/address/address.hpp | 2 +- .../stdlib/recursion/CMakeLists.txt | 2 +- .../verification_key/verification_key.hpp | 61 +++---- .../verification_key.test.cpp | 42 +++++ 7 files changed, 283 insertions(+), 63 deletions(-) create mode 100644 cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.test.cpp diff --git a/cpp/src/barretenberg/proof_system/verification_key/verification_key.cpp b/cpp/src/barretenberg/proof_system/verification_key/verification_key.cpp index 0209bc3a0c..e2582efacc 100644 --- a/cpp/src/barretenberg/proof_system/verification_key/verification_key.cpp +++ b/cpp/src/barretenberg/proof_system/verification_key/verification_key.cpp @@ -1,10 +1,44 @@ #include "barretenberg/crypto/sha256/sha256.hpp" #include "barretenberg/crypto/pedersen_commitment/pedersen.hpp" +#include "barretenberg/crypto/pedersen_commitment/pedersen_lookup.hpp" +#include "barretenberg/polynomials/evaluation_domain.hpp" #include "verification_key.hpp" #include "../../plonk/proof_system/constants.hpp" namespace bonk { +/** + * @brief Hashes the evaluation domain to match the 'circuit' approach taken in + * stdlib/recursion/verification_key/verification_key.hpp. + * @note: in that reference file, the circuit-equivalent of this function is a _method_ of the `evaluation_domain' + * struct. But we cannot do that with the native `barretenberg::evaluation_domain` type unfortunately, because it's + * defined in polynomials/evaluation_domain.hpp, and `polynomial` is a bberg library which does not depend on `crypto` + * in its CMakeLists.txt file. (We'd need `crypto` to be able to call native pedersen functions). + * + * @param domain to compress + * @param composer_type to use when choosing pedersen compression function + * @return barretenberg::fr compression of the evaluation domain as a field + */ +barretenberg::fr compress_native_evaluation_domain(barretenberg::evaluation_domain const& domain, + plonk::ComposerType composer_type) +{ + barretenberg::fr out; + if (composer_type == plonk::ComposerType::PLOOKUP) { + out = crypto::pedersen_commitment::lookup::compress_native({ + domain.root, + domain.domain, + domain.generator, + }); + } else { + out = crypto::pedersen_commitment::compress_native({ + domain.root, + domain.domain, + domain.generator, + }); + } + return out; +} + /** * @brief Compress the verification key data. * @@ -17,15 +51,46 @@ namespace bonk { */ barretenberg::fr verification_key_data::compress_native(const size_t hash_index) { + barretenberg::evaluation_domain domain = evaluation_domain(circuit_size); + barretenberg::fr compressed_domain = compress_native_evaluation_domain(domain, plonk::ComposerType(composer_type)); + + constexpr size_t num_limb_bits = plonk::NUM_LIMB_BITS_IN_FIELD_SIMULATION; + + const auto split_bigfield_limbs = [](const uint256_t& element) { + std::vector limbs; + limbs.push_back(element.slice(0, num_limb_bits)); + limbs.push_back(element.slice(num_limb_bits, num_limb_bits * 2)); + limbs.push_back(element.slice(num_limb_bits * 2, num_limb_bits * 3)); + limbs.push_back(element.slice(num_limb_bits * 3, num_limb_bits * 4)); + return limbs; + }; + std::vector preimage_data; preimage_data.emplace_back(composer_type); - preimage_data.emplace_back(circuit_size); + preimage_data.emplace_back(compressed_domain); preimage_data.emplace_back(num_public_inputs); - for (auto& commitment_entry : commitments) { - preimage_data.emplace_back(commitment_entry.second.x); - preimage_data.emplace_back(commitment_entry.second.y); + for (const auto& [tag, selector] : commitments) { + const auto x_limbs = split_bigfield_limbs(selector.x); + const auto y_limbs = split_bigfield_limbs(selector.y); + + preimage_data.push_back(x_limbs[0]); + preimage_data.push_back(x_limbs[1]); + preimage_data.push_back(x_limbs[2]); + preimage_data.push_back(x_limbs[3]); + + preimage_data.push_back(y_limbs[0]); + preimage_data.push_back(y_limbs[1]); + preimage_data.push_back(y_limbs[2]); + preimage_data.push_back(y_limbs[3]); + } + + barretenberg::fr compressed_key; + if (plonk::ComposerType(composer_type) == plonk::ComposerType::PLOOKUP) { + compressed_key = crypto::pedersen_commitment::lookup::compress_native(preimage_data, hash_index); + } else { + compressed_key = crypto::pedersen_commitment::compress_native(preimage_data, hash_index); } - return crypto::pedersen_commitment::compress_native(preimage_data, hash_index); + return compressed_key; } verification_key::verification_key(const size_t num_gates, diff --git a/cpp/src/barretenberg/proof_system/verification_key/verification_key.hpp b/cpp/src/barretenberg/proof_system/verification_key/verification_key.hpp index fc0e326863..bc3a77929f 100644 --- a/cpp/src/barretenberg/proof_system/verification_key/verification_key.hpp +++ b/cpp/src/barretenberg/proof_system/verification_key/verification_key.hpp @@ -116,13 +116,12 @@ template inline void write(B& buf, verification_key const& key) inline std::ostream& operator<<(std::ostream& os, verification_key const& key) { - return os - << "key.composer_type: " << key.composer_type << "\n" - << "key.circuit_size: " << static_cast(key.circuit_size) << "\n" - << "key.num_public_inputs: " << static_cast(key.num_public_inputs) << "\n" - << "key.commitments: " << key.commitments << "\n" - << "key.contains_recursive_proof: " << key.contains_recursive_proof << "\n" - << "key.recursive_proof_public_input_indices: " << key.recursive_proof_public_input_indices << "\n"; + return os << "key.composer_type: " << key.composer_type << "\n" + << "key.circuit_size: " << static_cast(key.circuit_size) << "\n" + << "key.num_public_inputs: " << static_cast(key.num_public_inputs) << "\n" + << "key.commitments: " << key.commitments << "\n" + << "key.contains_recursive_proof: " << key.contains_recursive_proof << "\n" + << "key.recursive_proof_public_input_indices: " << key.recursive_proof_public_input_indices << "\n"; }; } // namespace bonk diff --git a/cpp/src/barretenberg/proof_system/verification_key/verification_key.test.cpp b/cpp/src/barretenberg/proof_system/verification_key/verification_key.test.cpp index 8f74d875d6..abc5e277c6 100644 --- a/cpp/src/barretenberg/proof_system/verification_key/verification_key.test.cpp +++ b/cpp/src/barretenberg/proof_system/verification_key/verification_key.test.cpp @@ -1,43 +1,154 @@ #include "barretenberg/common/test.hpp" #include "barretenberg/common/streams.hpp" +#include "barretenberg/numeric/random/engine.hpp" #include "verification_key.hpp" +namespace { +auto& engine = numeric::random::get_debug_engine(); +} + using namespace barretenberg; using namespace bonk; +/** + * @brief generated a random vk data for use in tests + * + * @return verification_key_data randomly generated + */ +verification_key_data rand_vk_data() +{ + verification_key_data key_data; + key_data.composer_type = static_cast(plonk::ComposerType::STANDARD); + key_data.circuit_size = 1024; // not random - must be power of 2 + key_data.num_public_inputs = engine.get_random_uint32(); + key_data.commitments["test1"] = g1::element::random_element(); + key_data.commitments["test2"] = g1::element::random_element(); + key_data.commitments["foo1"] = g1::element::random_element(); + key_data.commitments["foo2"] = g1::element::random_element(); + return key_data; +} + +/** + * @brief expect that two vk data compressions are equal for a few different hash indices + * + * @param key0_data + * @param key1_data + */ +void expect_compressions_eq(verification_key_data key0_data, verification_key_data key1_data) +{ + // 0 hash index + EXPECT_EQ(key0_data.compress_native(0), key1_data.compress_native(0)); + // nonzero hash index + EXPECT_EQ(key0_data.compress_native(15), key1_data.compress_native(15)); +} + +/** + * @brief expect that two vk data compressions are not-equal for a few different hash indices + * + * @param key0_data + * @param key1_data + */ +void expect_compressions_ne(verification_key_data key0_data, verification_key_data key1_data) +{ + EXPECT_NE(key0_data.compress_native(0), key1_data.compress_native(0)); + EXPECT_NE(key0_data.compress_native(15), key1_data.compress_native(15)); + // ne hash indeces still lead to ne compressions + EXPECT_NE(key0_data.compress_native(0), key1_data.compress_native(15)); + EXPECT_NE(key0_data.compress_native(14), key1_data.compress_native(15)); +} + TEST(verification_key, buffer_serialization) { - verification_key_data key; - key.composer_type = static_cast(plonk::ComposerType::STANDARD); - key.circuit_size = 1234; - key.num_public_inputs = 10; - key.commitments["test1"] = g1::element::random_element(); - key.commitments["test2"] = g1::element::random_element(); - key.commitments["foo1"] = g1::element::random_element(); - key.commitments["foo2"] = g1::element::random_element(); + verification_key_data key_data = rand_vk_data(); - auto buf = to_buffer(key); + auto buf = to_buffer(key_data); auto result = from_buffer(buf); - EXPECT_EQ(key, result); + EXPECT_EQ(key_data, result); } TEST(verification_key, stream_serialization) { - verification_key_data key; - key.composer_type = static_cast(plonk::ComposerType::STANDARD); - key.circuit_size = 1234; - key.num_public_inputs = 10; - key.commitments["test1"] = g1::element::random_element(); - key.commitments["test2"] = g1::element::random_element(); - key.commitments["foo1"] = g1::element::random_element(); - key.commitments["foo2"] = g1::element::random_element(); + verification_key_data key_data = rand_vk_data(); std::stringstream s; - write(s, key); + write(s, key_data); verification_key_data result; read(static_cast(s), result); - EXPECT_EQ(key, result); + EXPECT_EQ(key_data, result); +} + +TEST(verification_key, basic_compression_equality) +{ + verification_key_data key0_data = rand_vk_data(); + verification_key_data key1_data = key0_data; // copy + expect_compressions_eq(key0_data, key1_data); +} + +TEST(verification_key, compression_inequality_index_mismatch) +{ + verification_key_data key0_data = rand_vk_data(); + verification_key_data key1_data = key0_data; // copy + // inquality on hash index mismatch + EXPECT_NE(key0_data.compress_native(0), key1_data.compress_native(15)); + EXPECT_NE(key0_data.compress_native(14), key1_data.compress_native(15)); +} + +TEST(verification_key, compression_inequality_composer_type) +{ + verification_key_data key0_data = rand_vk_data(); + verification_key_data key1_data = key0_data; // copy + key0_data.composer_type = static_cast(plonk::ComposerType::PLOOKUP); + expect_compressions_ne(key0_data, key1_data); +} + +TEST(verification_key, compression_inequality_different_circuit_size) +{ + verification_key_data key0_data = rand_vk_data(); + verification_key_data key1_data = key0_data; + key0_data.circuit_size = 4096; + expect_compressions_ne(key0_data, key1_data); +} + +TEST(verification_key, compression_inequality_different_num_public_inputs) +{ + verification_key_data key0_data = rand_vk_data(); + verification_key_data key1_data = key0_data; + key0_data.num_public_inputs = 42; + expect_compressions_ne(key0_data, key1_data); +} + +TEST(verification_key, compression_inequality_different_commitments) +{ + verification_key_data key0_data = rand_vk_data(); + verification_key_data key1_data = key0_data; + key0_data.commitments["test1"] = g1::element::random_element(); + expect_compressions_ne(key0_data, key1_data); +} + +TEST(verification_key, compression_inequality_different_num_commitments) +{ + verification_key_data key0_data = rand_vk_data(); + verification_key_data key1_data = key0_data; + key0_data.commitments["new"] = g1::element::random_element(); + expect_compressions_ne(key0_data, key1_data); +} + +TEST(verification_key, compression_equality_different_contains_recursive_proof) +{ + verification_key_data key0_data = rand_vk_data(); + verification_key_data key1_data = key0_data; + key0_data.contains_recursive_proof = false; + key1_data.contains_recursive_proof = true; + expect_compressions_eq(key0_data, key1_data); +} + +TEST(verification_key, compression_equality_different_recursive_proof_public_input_indices) +{ + verification_key_data key0_data = rand_vk_data(); + verification_key_data key1_data = key0_data; + key1_data.recursive_proof_public_input_indices.push_back(42); + expect_compressions_eq(key0_data, key1_data); } diff --git a/cpp/src/barretenberg/stdlib/primitives/address/address.hpp b/cpp/src/barretenberg/stdlib/primitives/address/address.hpp index f50c890f0c..fb2dd57d39 100644 --- a/cpp/src/barretenberg/stdlib/primitives/address/address.hpp +++ b/cpp/src/barretenberg/stdlib/primitives/address/address.hpp @@ -54,7 +54,7 @@ template void read(B& it, address& addr) using serialize::read; fr address_field; read(it, address_field); - addr = address_field; + addr = address(address_field); } template void write(B& buf, address const& addr) diff --git a/cpp/src/barretenberg/stdlib/recursion/CMakeLists.txt b/cpp/src/barretenberg/stdlib/recursion/CMakeLists.txt index c5d1124e59..325069d446 100644 --- a/cpp/src/barretenberg/stdlib/recursion/CMakeLists.txt +++ b/cpp/src/barretenberg/stdlib/recursion/CMakeLists.txt @@ -1 +1 @@ -barretenberg_module(stdlib_recursion ecc plonk stdlib_primitives stdlib_pedersen_commitment stdlib_blake3s) \ No newline at end of file +barretenberg_module(stdlib_recursion ecc proof_system stdlib_primitives stdlib_pedersen_commitment stdlib_blake3s) \ No newline at end of file diff --git a/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.hpp b/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.hpp index df8e5b21b0..68c7abc620 100644 --- a/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.hpp +++ b/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.hpp @@ -5,7 +5,6 @@ #include "barretenberg/proof_system/types/polynomial_manifest.hpp" -#include "barretenberg/plonk/proof_system/utils/kate_verification.hpp" #include "barretenberg/plonk/proof_system/public_inputs/public_inputs.hpp" #include "barretenberg/polynomials/polynomial_arithmetic.hpp" @@ -188,34 +187,36 @@ template struct verification_key { } public: - field_t compress() + field_t compress(size_t const hash_index = 0) { field_t compressed_domain = domain.compress(); - std::vector> key_witnesses; - key_witnesses.push_back(compressed_domain); - key_witnesses.push_back(num_public_inputs); + std::vector> preimage_data; + preimage_data.push_back(Composer::type); + preimage_data.push_back(compressed_domain); + preimage_data.push_back(num_public_inputs); for (const auto& [tag, selector] : commitments) { - key_witnesses.push_back(selector.x.binary_basis_limbs[0].element); - key_witnesses.push_back(selector.x.binary_basis_limbs[1].element); - key_witnesses.push_back(selector.x.binary_basis_limbs[2].element); - key_witnesses.push_back(selector.x.binary_basis_limbs[3].element); - key_witnesses.push_back(selector.y.binary_basis_limbs[0].element); - key_witnesses.push_back(selector.y.binary_basis_limbs[1].element); - key_witnesses.push_back(selector.y.binary_basis_limbs[2].element); - key_witnesses.push_back(selector.y.binary_basis_limbs[3].element); + preimage_data.push_back(selector.x.binary_basis_limbs[0].element); + preimage_data.push_back(selector.x.binary_basis_limbs[1].element); + preimage_data.push_back(selector.x.binary_basis_limbs[2].element); + preimage_data.push_back(selector.x.binary_basis_limbs[3].element); + preimage_data.push_back(selector.y.binary_basis_limbs[0].element); + preimage_data.push_back(selector.y.binary_basis_limbs[1].element); + preimage_data.push_back(selector.y.binary_basis_limbs[2].element); + preimage_data.push_back(selector.y.binary_basis_limbs[3].element); } field_t compressed_key; if constexpr (Composer::type == ComposerType::PLOOKUP) { - compressed_key = pedersen_plookup_commitment::compress(key_witnesses); + compressed_key = pedersen_plookup_commitment::compress(preimage_data, hash_index); } else { - compressed_key = pedersen_commitment::compress(key_witnesses); + compressed_key = pedersen_commitment::compress(preimage_data, hash_index); } return compressed_key; } - static barretenberg::fr compress_native(const std::shared_ptr& key) + static barretenberg::fr compress_native(const std::shared_ptr& key, + const size_t hash_index = 0) { barretenberg::fr compressed_domain = evaluation_domain::compress_native(key->domain); @@ -229,28 +230,30 @@ template struct verification_key { return limbs; }; - std::vector key_witnesses; - key_witnesses.push_back(compressed_domain); - key_witnesses.push_back(key->num_public_inputs); + std::vector preimage_data; + preimage_data.push_back(Composer::type); + preimage_data.push_back(compressed_domain); + preimage_data.push_back(key->num_public_inputs); for (const auto& [tag, selector] : key->commitments) { const auto x_limbs = split_bigfield_limbs(selector.x); const auto y_limbs = split_bigfield_limbs(selector.y); - key_witnesses.push_back(x_limbs[0]); - key_witnesses.push_back(x_limbs[1]); - key_witnesses.push_back(x_limbs[2]); - key_witnesses.push_back(x_limbs[3]); + preimage_data.push_back(x_limbs[0]); + preimage_data.push_back(x_limbs[1]); + preimage_data.push_back(x_limbs[2]); + preimage_data.push_back(x_limbs[3]); - key_witnesses.push_back(y_limbs[0]); - key_witnesses.push_back(y_limbs[1]); - key_witnesses.push_back(y_limbs[2]); - key_witnesses.push_back(y_limbs[3]); + preimage_data.push_back(y_limbs[0]); + preimage_data.push_back(y_limbs[1]); + preimage_data.push_back(y_limbs[2]); + preimage_data.push_back(y_limbs[3]); } + barretenberg::fr compressed_key; if constexpr (Composer::type == ComposerType::PLOOKUP) { - compressed_key = crypto::pedersen_commitment::lookup::compress_native(key_witnesses); + compressed_key = crypto::pedersen_commitment::lookup::compress_native(preimage_data, hash_index); } else { - compressed_key = crypto::pedersen_commitment::compress_native(key_witnesses); + compressed_key = crypto::pedersen_commitment::compress_native(preimage_data, hash_index); } return compressed_key; } diff --git a/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.test.cpp b/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.test.cpp new file mode 100644 index 0000000000..b30a0d64d4 --- /dev/null +++ b/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.test.cpp @@ -0,0 +1,42 @@ +#include "verification_key.hpp" +#include + +#include "barretenberg/ecc/curves/bn254/fr.hpp" +#include "barretenberg/ecc/curves/bn254/g1.hpp" +#include "barretenberg/proof_system/verification_key/verification_key.hpp" +#include "barretenberg/plonk/proof_system/constants.hpp" +#include "barretenberg/stdlib/types/types.hpp" + +using namespace plonk; + +namespace { +auto& engine = numeric::random::get_debug_engine(); +} + +verification_key_data rand_vk_data(plonk::ComposerType composer_type) +{ + verification_key_data key_data; + key_data.composer_type = static_cast(composer_type); + key_data.circuit_size = 1024; // not random - must be power of 2 + key_data.num_public_inputs = engine.get_random_uint16(); + key_data.commitments["test1"] = g1::element::random_element(); + key_data.commitments["test2"] = g1::element::random_element(); + key_data.commitments["foo1"] = g1::element::random_element(); + key_data.commitments["foo2"] = g1::element::random_element(); + return key_data; +} + +TEST(stdlib_verification_key, compress_native_comparison) +{ + // Compute compression of native verification key (i.e. vk_data) + auto crs = std::make_unique("../srs_db/ignition"); + verification_key_data vk_data = rand_vk_data(stdlib::types::Composer::type); + const size_t hash_idx = 10; + auto native_vk_compression = vk_data.compress_native(hash_idx); + + // Compute compression of recursive verification key + auto verification_key = std::make_shared(std::move(vk_data), crs->get_verifier_crs()); + auto recursive_vk_compression = + stdlib::recursion::verification_key::compress_native(verification_key, hash_idx); + EXPECT_EQ(native_vk_compression, recursive_vk_compression); +} \ No newline at end of file From 6c101be134bbea01deed88a5085ebd35e6f62796 Mon Sep 17 00:00:00 2001 From: Suyash Bagad Date: Tue, 4 Apr 2023 02:43:59 +0100 Subject: [PATCH 4/4] inc num_generators_per_hash_index to 128. (#309) --- cpp/src/barretenberg/crypto/generators/generator_data.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/barretenberg/crypto/generators/generator_data.cpp b/cpp/src/barretenberg/crypto/generators/generator_data.cpp index db6c676336..7970dfe659 100644 --- a/cpp/src/barretenberg/crypto/generators/generator_data.cpp +++ b/cpp/src/barretenberg/crypto/generators/generator_data.cpp @@ -13,7 +13,7 @@ constexpr size_t num_hash_indices = 32; #else constexpr size_t num_default_generators = 2048; constexpr size_t num_hash_indices = 32; -constexpr size_t num_generators_per_hash_index = 64; +constexpr size_t num_generators_per_hash_index = 128; #endif constexpr size_t hash_indices_generator_offset = 2048;