From c94730d178b09a7591732e6636dd7eb76b8cf6eb Mon Sep 17 00:00:00 2001 From: Innokentii Sennovskii Date: Thu, 27 Apr 2023 21:16:54 +0100 Subject: [PATCH] Ultraplonk check_circuit (https://github.com/AztecProtocol/barretenberg/pull/366) * Add check_circuit with mid-construction introspection --- circuits/cpp/barretenberg/cpp/CMakeLists.txt | 1 + .../ultra_honk_composer_helper.cpp | 2 +- .../honk/composer/ultra_honk_composer.hpp | 16 +- .../composer/ultra_honk_composer.test.cpp | 2 +- .../ultra_plonk_composer_helper.cpp | 6 +- .../ultra_plonk_composer_helper.hpp | 2 +- .../splitting_tmp/ultra_plonk_composer.hpp | 16 +- .../ultra_plonk_composer.test.cpp | 2 +- .../plonk/composer/ultra_composer.cpp | 9 +- .../plonk/composer/ultra_composer.hpp | 2 +- .../plonk/composer/ultra_composer.test.cpp | 2 +- .../barretenberg/proof_system/CMakeLists.txt | 2 +- .../ultra_circuit_constructor.cpp | 933 +++++++++++++++++- .../ultra_circuit_constructor.hpp | 745 ++++++++++---- .../ultra_circuit_constructor.test.cpp | 838 ++++++++++++++++ .../plookup_tables/aes128.hpp | 0 .../plookup_tables/blake2s.hpp | 0 .../plookup_tables/keccak/keccak_chi.hpp | 0 .../plookup_tables/keccak/keccak_input.hpp | 0 .../plookup_tables/keccak/keccak_output.hpp | 0 .../plookup_tables/keccak/keccak_rho.hpp | 0 .../plookup_tables/keccak/keccak_theta.hpp | 0 .../non_native_group_generator.cpp | 0 .../non_native_group_generator.hpp | 0 .../plookup_tables/pedersen.hpp | 0 .../plookup_tables/plookup_tables.cpp | 0 .../plookup_tables/plookup_tables.hpp | 0 .../plookup_tables/sha256.hpp | 0 .../plookup_tables/sparse.hpp | 0 .../plookup_tables/types.hpp | 0 .../plookup_tables/uint.hpp | 0 .../commitment/pedersen/pedersen_plookup.cpp | 2 +- .../stdlib/hash/blake2s/blake2s_plookup.cpp | 4 +- .../stdlib/hash/blake2s/blake2s_plookup.hpp | 2 +- .../stdlib/hash/blake2s/blake_util.hpp | 2 +- .../stdlib/hash/blake3s/blake3s_plookup.cpp | 2 +- .../stdlib/hash/blake3s/blake3s_plookup.hpp | 2 +- .../stdlib/hash/pedersen/pedersen_plookup.cpp | 2 +- .../stdlib/hash/sha256/sha256.test.cpp | 2 +- .../stdlib/hash/sha256/sha256_plookup.cpp | 4 +- .../stdlib/hash/sha256/sha256_plookup.hpp | 2 +- .../stdlib/primitives/bigfield/bigfield.hpp | 2 +- .../stdlib/primitives/plookup/plookup.cpp | 4 +- .../stdlib/primitives/plookup/plookup.hpp | 4 +- 44 files changed, 2366 insertions(+), 246 deletions(-) create mode 100644 circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/circuit_constructors/ultra_circuit_constructor.test.cpp rename circuits/cpp/barretenberg/cpp/src/barretenberg/{plonk/composer => proof_system}/plookup_tables/aes128.hpp (100%) rename circuits/cpp/barretenberg/cpp/src/barretenberg/{plonk/composer => proof_system}/plookup_tables/blake2s.hpp (100%) rename circuits/cpp/barretenberg/cpp/src/barretenberg/{plonk/composer => proof_system}/plookup_tables/keccak/keccak_chi.hpp (100%) rename circuits/cpp/barretenberg/cpp/src/barretenberg/{plonk/composer => proof_system}/plookup_tables/keccak/keccak_input.hpp (100%) rename circuits/cpp/barretenberg/cpp/src/barretenberg/{plonk/composer => proof_system}/plookup_tables/keccak/keccak_output.hpp (100%) rename circuits/cpp/barretenberg/cpp/src/barretenberg/{plonk/composer => proof_system}/plookup_tables/keccak/keccak_rho.hpp (100%) rename circuits/cpp/barretenberg/cpp/src/barretenberg/{plonk/composer => proof_system}/plookup_tables/keccak/keccak_theta.hpp (100%) rename circuits/cpp/barretenberg/cpp/src/barretenberg/{plonk/composer => proof_system}/plookup_tables/non_native_group_generator.cpp (100%) rename circuits/cpp/barretenberg/cpp/src/barretenberg/{plonk/composer => proof_system}/plookup_tables/non_native_group_generator.hpp (100%) rename circuits/cpp/barretenberg/cpp/src/barretenberg/{plonk/composer => proof_system}/plookup_tables/pedersen.hpp (100%) rename circuits/cpp/barretenberg/cpp/src/barretenberg/{plonk/composer => proof_system}/plookup_tables/plookup_tables.cpp (100%) rename circuits/cpp/barretenberg/cpp/src/barretenberg/{plonk/composer => proof_system}/plookup_tables/plookup_tables.hpp (100%) rename circuits/cpp/barretenberg/cpp/src/barretenberg/{plonk/composer => proof_system}/plookup_tables/sha256.hpp (100%) rename circuits/cpp/barretenberg/cpp/src/barretenberg/{plonk/composer => proof_system}/plookup_tables/sparse.hpp (100%) rename circuits/cpp/barretenberg/cpp/src/barretenberg/{plonk/composer => proof_system}/plookup_tables/types.hpp (100%) rename circuits/cpp/barretenberg/cpp/src/barretenberg/{plonk/composer => proof_system}/plookup_tables/uint.hpp (100%) diff --git a/circuits/cpp/barretenberg/cpp/CMakeLists.txt b/circuits/cpp/barretenberg/cpp/CMakeLists.txt index 4f719a03357..606dbd6f657 100644 --- a/circuits/cpp/barretenberg/cpp/CMakeLists.txt +++ b/circuits/cpp/barretenberg/cpp/CMakeLists.txt @@ -38,6 +38,7 @@ endif() if(ENABLE_ASAN) add_compile_options(-fsanitize=address) add_link_options(-fsanitize=address) + set(DISABLE_ASM ON) endif() if(SERIALIZE_CANARY) diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/composer/composer_helper/ultra_honk_composer_helper.cpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/composer/composer_helper/ultra_honk_composer_helper.cpp index b1c5850b04b..dce07006c41 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/composer/composer_helper/ultra_honk_composer_helper.cpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/composer/composer_helper/ultra_honk_composer_helper.cpp @@ -37,7 +37,7 @@ void UltraHonkComposerHelper::compute_witness(CircuitConstru const size_t filled_gates = circuit_constructor.num_gates + circuit_constructor.public_inputs.size(); const size_t total_num_gates = std::max(filled_gates, tables_size + lookups_size); - const size_t subgroup_size = circuit_constructor.get_circuit_subgroup_size(total_num_gates + NUM_RESERVED_GATES); + const size_t subgroup_size = circuit_constructor.get_circuit_subgroup_size(total_num_gates + NUM_RANDOMIZED_GATES); // Pad the wires (pointers to `witness_indices` of the `variables` vector). // Note: the remaining NUM_RESERVED_GATES indices are padded with zeros within `compute_witness_base` (called diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/composer/ultra_honk_composer.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/composer/ultra_honk_composer.hpp index f2eb0300390..dfdf5ff8dae 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/composer/ultra_honk_composer.hpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/composer/ultra_honk_composer.hpp @@ -1,5 +1,5 @@ #pragma once -#include "barretenberg/plonk/composer/plookup_tables/plookup_tables.hpp" +#include "barretenberg/proof_system/plookup_tables/plookup_tables.hpp" #include "barretenberg/honk/proof_system/ultra_prover.hpp" #include "barretenberg/proof_system/circuit_constructors/ultra_circuit_constructor.hpp" #include "barretenberg/honk/composer/composer_helper/ultra_honk_composer_helper.hpp" @@ -289,7 +289,7 @@ class UltraHonkComposer { std::vector decompose_into_default_range( const uint32_t variable_index, const uint64_t num_bits, - const uint64_t target_range_bitnum = DEFAULT_PLOOKUP_RANGE_BITNUM, + const uint64_t target_range_bitnum = UltraCircuitConstructor::DEFAULT_PLOOKUP_RANGE_BITNUM, std::string const& msg = "decompose_into_default_range") { return circuit_constructor.decompose_into_default_range(variable_index, num_bits, target_range_bitnum, msg); @@ -348,17 +348,19 @@ class UltraHonkComposer { // /** // * Non Native Field Arithmetic // **/ - void range_constrain_two_limbs(const uint32_t lo_idx, - const uint32_t hi_idx, - const size_t lo_limb_bits = DEFAULT_NON_NATIVE_FIELD_LIMB_BITS, - const size_t hi_limb_bits = DEFAULT_NON_NATIVE_FIELD_LIMB_BITS) + void range_constrain_two_limbs( + const uint32_t lo_idx, + const uint32_t hi_idx, + const size_t lo_limb_bits = UltraCircuitConstructor::DEFAULT_NON_NATIVE_FIELD_LIMB_BITS, + const size_t hi_limb_bits = UltraCircuitConstructor::DEFAULT_NON_NATIVE_FIELD_LIMB_BITS) { circuit_constructor.range_constrain_two_limbs(lo_idx, hi_idx, lo_limb_bits, hi_limb_bits); }; // std::array decompose_non_native_field_double_width_limb( // const uint32_t limb_idx, const size_t num_limb_bits = (2 * DEFAULT_NON_NATIVE_FIELD_LIMB_BITS)); std::array queue_non_native_field_multiplication( - const non_native_field_witnesses& input, const bool range_constrain_quotient_and_remainder = true) + const UltraCircuitConstructor::non_native_field_witnesses& input, + const bool range_constrain_quotient_and_remainder = true) { return circuit_constructor.queue_non_native_field_multiplication(input, range_constrain_quotient_and_remainder); }; diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/composer/ultra_honk_composer.test.cpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/composer/ultra_honk_composer.test.cpp index 7e857dd490d..a36d563a794 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/composer/ultra_honk_composer.test.cpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/composer/ultra_honk_composer.test.cpp @@ -748,7 +748,7 @@ TEST(UltraHonkComposer, non_native_field_multiplication) const auto q_indices = get_limb_witness_indices(split_into_limbs(uint256_t(q))); const auto r_indices = get_limb_witness_indices(split_into_limbs(uint256_t(r))); - proof_system::non_native_field_witnesses inputs{ + proof_system::UltraCircuitConstructor::non_native_field_witnesses inputs{ a_indices, b_indices, q_indices, r_indices, modulus_limbs, fr(uint256_t(modulus)), }; const auto [lo_1_idx, hi_1_idx] = honk_composer.queue_non_native_field_multiplication(inputs); diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/splitting_tmp/composer_helper/ultra_plonk_composer_helper.cpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/splitting_tmp/composer_helper/ultra_plonk_composer_helper.cpp index 26e7049443f..48f2bc64cb3 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/splitting_tmp/composer_helper/ultra_plonk_composer_helper.cpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/splitting_tmp/composer_helper/ultra_plonk_composer_helper.cpp @@ -50,7 +50,7 @@ void UltraPlonkComposerHelper::compute_witness(CircuitConstr // TODO(luke): subgroup size was already computed above but compute_witness_base computes it again. If we pass in // NUM_RANDOMIZED_GATES (as in the other split composers) the resulting sizes can differ. Reconcile this. - auto wire_polynomial_evaluations = compute_witness_base(circuit_constructor, total_num_gates, NUM_RANDOMIZED_GATES); + auto wire_polynomial_evaluations = compute_witness_base(circuit_constructor, total_num_gates, NUM_RESERVED_GATES); for (size_t j = 0; j < program_width; ++j) { std::string index = std::to_string(j + 1); @@ -225,7 +225,7 @@ std::shared_ptr UltraPlonkComposerHelper::compu } const size_t minimum_circuit_size = tables_size + lookups_size; - const size_t num_randomized_gates = NUM_RANDOMIZED_GATES; + const size_t num_randomized_gates = NUM_RESERVED_GATES; // Initialize circuit_proving_key // TODO(#229)(Kesha): replace composer types. circuit_proving_key = initialize_proving_key( @@ -359,7 +359,7 @@ std::shared_ptr UltraPlonkComposerHelperget_verifier_crs()); - circuit_verification_key->composer_type = type; // Invariably plookup for this class. + circuit_verification_key->composer_type = ComposerType::PLOOKUP; // Invariably plookup for this class. // See `add_recusrive_proof()` for how this recursive data is assigned. circuit_verification_key->recursive_proof_public_input_indices = diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/splitting_tmp/composer_helper/ultra_plonk_composer_helper.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/splitting_tmp/composer_helper/ultra_plonk_composer_helper.hpp index c66ce7eea0b..0c0f155b15a 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/splitting_tmp/composer_helper/ultra_plonk_composer_helper.hpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/splitting_tmp/composer_helper/ultra_plonk_composer_helper.hpp @@ -20,7 +20,7 @@ template class UltraPlonkComposerHelper { // NUM_RESERVED_GATES). Therefore for consistency within this composer itself, and consistency with the original // Ultra Composer, this value must match that of NUM_RESERVED_GATES. This issue needs to be reconciled // simultaneously here and in the other split composers. - static constexpr size_t NUM_RANDOMIZED_GATES = 4; // equal to the number of multilinear evaluations leaked + static constexpr size_t NUM_RESERVED_GATES = 4; // equal to the number of multilinear evaluations leaked static constexpr size_t program_width = CircuitConstructor::program_width; std::shared_ptr circuit_proving_key; std::shared_ptr circuit_verification_key; diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/splitting_tmp/ultra_plonk_composer.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/splitting_tmp/ultra_plonk_composer.hpp index dd51d0aa3dd..73a5f6ed94c 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/splitting_tmp/ultra_plonk_composer.hpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/splitting_tmp/ultra_plonk_composer.hpp @@ -1,6 +1,6 @@ #pragma once #include "barretenberg/plonk/composer/composer_base.hpp" -#include "barretenberg/plonk/composer/plookup_tables/plookup_tables.hpp" +#include "barretenberg/proof_system/plookup_tables/plookup_tables.hpp" #include "barretenberg/plonk/proof_system/prover/prover.hpp" #include "barretenberg/proof_system/circuit_constructors/ultra_circuit_constructor.hpp" #include "barretenberg/proof_system/types/merkle_hash_type.hpp" @@ -311,7 +311,7 @@ class UltraPlonkComposer { std::vector decompose_into_default_range( const uint32_t variable_index, const uint64_t num_bits, - const uint64_t target_range_bitnum = DEFAULT_PLOOKUP_RANGE_BITNUM, + const uint64_t target_range_bitnum = UltraCircuitConstructor::DEFAULT_PLOOKUP_RANGE_BITNUM, std::string const& msg = "decompose_into_default_range") { return circuit_constructor.decompose_into_default_range(variable_index, num_bits, target_range_bitnum, msg); @@ -370,17 +370,19 @@ class UltraPlonkComposer { // /** // * Non Native Field Arithmetic // **/ - void range_constrain_two_limbs(const uint32_t lo_idx, - const uint32_t hi_idx, - const size_t lo_limb_bits = DEFAULT_NON_NATIVE_FIELD_LIMB_BITS, - const size_t hi_limb_bits = DEFAULT_NON_NATIVE_FIELD_LIMB_BITS) + void range_constrain_two_limbs( + const uint32_t lo_idx, + const uint32_t hi_idx, + const size_t lo_limb_bits = UltraCircuitConstructor::DEFAULT_NON_NATIVE_FIELD_LIMB_BITS, + const size_t hi_limb_bits = UltraCircuitConstructor::DEFAULT_NON_NATIVE_FIELD_LIMB_BITS) { circuit_constructor.range_constrain_two_limbs(lo_idx, hi_idx, lo_limb_bits, hi_limb_bits); }; // std::array decompose_non_native_field_double_width_limb( // const uint32_t limb_idx, const size_t num_limb_bits = (2 * DEFAULT_NON_NATIVE_FIELD_LIMB_BITS)); std::array queue_non_native_field_multiplication( - const non_native_field_witnesses& input, const bool range_constrain_quotient_and_remainder = true) + const UltraCircuitConstructor::non_native_field_witnesses& input, + const bool range_constrain_quotient_and_remainder = true) { return circuit_constructor.queue_non_native_field_multiplication(input, range_constrain_quotient_and_remainder); }; diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/splitting_tmp/ultra_plonk_composer.test.cpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/splitting_tmp/ultra_plonk_composer.test.cpp index cac3e754ae0..1e697f07d44 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/splitting_tmp/ultra_plonk_composer.test.cpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/splitting_tmp/ultra_plonk_composer.test.cpp @@ -780,7 +780,7 @@ TEST(ultra_plonk_composer_splitting_tmp, non_native_field_multiplication) const auto q_indices = get_limb_witness_indices(split_into_limbs(uint256_t(q))); const auto r_indices = get_limb_witness_indices(split_into_limbs(uint256_t(r))); - non_native_field_witnesses inputs{ + UltraCircuitConstructor::non_native_field_witnesses inputs{ a_indices, b_indices, q_indices, r_indices, modulus_limbs, fr(uint256_t(modulus)), }; const auto [lo_1_idx, hi_1_idx] = composer.queue_non_native_field_multiplication(inputs); diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/ultra_composer.cpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/ultra_composer.cpp index ad6fd54f9ab..1f3e9fb3ed3 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/ultra_composer.cpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/ultra_composer.cpp @@ -14,10 +14,10 @@ #include "barretenberg/plonk/proof_system/commitment_scheme/kate_commitment_scheme.hpp" #include "barretenberg/srs/reference_string/file_reference_string.hpp" -#include "plookup_tables/types.hpp" -#include "plookup_tables/plookup_tables.hpp" -#include "plookup_tables/aes128.hpp" -#include "plookup_tables/sha256.hpp" +#include "barretenberg/proof_system/plookup_tables/types.hpp" +#include "barretenberg/proof_system/plookup_tables/plookup_tables.hpp" +#include "barretenberg/proof_system/plookup_tables/aes128.hpp" +#include "barretenberg/proof_system/plookup_tables/sha256.hpp" #ifndef NO_TBB #include @@ -1845,6 +1845,7 @@ std::array UltraComposer::decompose_non_native_field_double_width_l const uint256_t value = get_variable(limb_idx); const uint256_t low = value & LIMB_MASK; const uint256_t hi = value >> DEFAULT_NON_NATIVE_FIELD_LIMB_BITS; + // WTF(kesha): What is this supposed to do? Unless uint256_t has failed, this should always work ASSERT(low + (hi << DEFAULT_NON_NATIVE_FIELD_LIMB_BITS) == value); const uint32_t low_idx = add_variable(low); diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/ultra_composer.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/ultra_composer.hpp index d8988e3fcac..6f22b40bb00 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/ultra_composer.hpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/ultra_composer.hpp @@ -2,7 +2,7 @@ #include "composer_base.hpp" #include "barretenberg/proof_system/types/merkle_hash_type.hpp" #include "barretenberg/proof_system/types/pedersen_commitment_type.hpp" -#include "plookup_tables/plookup_tables.hpp" +#include "barretenberg/proof_system/plookup_tables/plookup_tables.hpp" #include namespace proof_system::plonk { diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/ultra_composer.test.cpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/ultra_composer.test.cpp index 9745d397e29..0ec324aa474 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/ultra_composer.test.cpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/ultra_composer.test.cpp @@ -5,7 +5,7 @@ #include "barretenberg/numeric/bitop/get_msb.hpp" #include "barretenberg/numeric/uintx/uintx.hpp" #include "../proof_system/widgets/random_widgets/plookup_widget.hpp" -#include "./plookup_tables/sha256.hpp" +#include "barretenberg/proof_system/plookup_tables/sha256.hpp" using namespace barretenberg; using namespace proof_system; diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/CMakeLists.txt b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/CMakeLists.txt index 3a8ed4dd8b3..c4e00489caf 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/CMakeLists.txt +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/CMakeLists.txt @@ -1 +1 @@ -barretenberg_module(proof_system polynomials crypto_generators) \ No newline at end of file +barretenberg_module(proof_system polynomials crypto_generators crypto_pedersen_hash) \ No newline at end of file diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/circuit_constructors/ultra_circuit_constructor.cpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/circuit_constructors/ultra_circuit_constructor.cpp index 425efbdb58a..8cbea71bc5b 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/circuit_constructors/ultra_circuit_constructor.cpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/circuit_constructors/ultra_circuit_constructor.cpp @@ -1,4 +1,14 @@ +/** + * @file ultra_circuit_constructor.cpp + * @author Luke (ledwards2225) and Kesha (Rumata888) + * @brief This file contains the implementation of UltraCircuitConstructor class that defines the logic of ultra-style + * circuits and is intended for the use in UltraHonk and UltraPlonk systems + * + * @todo 1) Replace barretenberg::fr with templated FF or Field + * + */ #include "ultra_circuit_constructor.hpp" +#include #include #include @@ -82,7 +92,6 @@ void UltraCircuitConstructor::create_add_gate(const add_triple& in) void UltraCircuitConstructor::create_big_add_gate(const add_quad& in, const bool include_next_gate_w_4) { assert_valid_variables({ in.a, in.b, in.c, in.d }); - w_l.emplace_back(in.a); w_r.emplace_back(in.b); w_o.emplace_back(in.c); @@ -489,7 +498,7 @@ plookup::ReadData UltraCircuitConstructor::create_gates_from_plookup_a * Generalized Permutation Methods **/ -RangeList UltraCircuitConstructor::create_range_list(const uint64_t target_range) +UltraCircuitConstructor::RangeList UltraCircuitConstructor::create_range_list(const uint64_t target_range) { RangeList result; const auto range_tag = get_new_tag(); // current_tag + 1; @@ -638,6 +647,7 @@ void UltraCircuitConstructor::create_new_range_constraint(const uint32_t variabl const uint64_t target_range, std::string const msg) { + if (uint256_t(get_variable(variable_index)).data[0] > target_range) { if (!failed()) { failure(msg); @@ -647,9 +657,44 @@ void UltraCircuitConstructor::create_new_range_constraint(const uint32_t variabl range_lists.insert({ target_range, create_range_list(target_range) }); } + const auto existing_tag = real_variable_tags[real_variable_index[variable_index]]; auto& list = range_lists[target_range]; - assign_tag(variable_index, list.range_tag); - list.variable_indices.emplace_back(variable_index); + + // If the variable's tag matches the target range list's tag, do nothing. + if (existing_tag != list.range_tag) { + // If the variable is 'untagged' (i.e., it has the dummy tag), assign it the appropriate tag. + // Otherwise, find the range for which the variable has already been tagged. + if (existing_tag != DUMMY_TAG) { + bool found_tag = false; + for (const auto& r : range_lists) { + if (r.second.range_tag == existing_tag) { + found_tag = true; + if (r.first < target_range) { + // The variable already has a more restrictive range check, so do nothing. + return; + } else { + // The range constraint we are trying to impose is more restrictive than the existing range + // constraint. It would be difficult to remove an existing range check. Instead deep-copy the + // variable and apply a range check to new variable + const uint32_t copied_witness = add_variable(get_variable(variable_index)); + create_add_gate({ .a = variable_index, + .b = copied_witness, + .c = zero_idx, + .a_scaling = 1, + .b_scaling = -1, + .c_scaling = 0, + .const_scaling = 0 }); + // Recurse with new witness that has no tag attached. + create_new_range_constraint(copied_witness, target_range, msg); + return; + } + } + } + ASSERT(found_tag == true); + } + assign_tag(variable_index, list.range_tag); + list.variable_indices.emplace_back(variable_index); + } } void UltraCircuitConstructor::process_range_list(const RangeList& list) @@ -1804,7 +1849,6 @@ void UltraCircuitConstructor::create_sorted_ROM_gate(RomRecord& record) w_r.emplace_back(record.value_column1_witness); w_o.emplace_back(record.value_column2_witness); w_4.emplace_back(record.record_witness); - record.gate_index = num_gates; ++num_gates; } @@ -2136,6 +2180,7 @@ uint32_t UltraCircuitConstructor::read_ROM_array(const size_t rom_id, const uint */ void UltraCircuitConstructor::process_ROM_array(const size_t rom_id, const size_t gate_offset_from_public_inputs) { + auto& rom_array = rom_arrays[rom_id]; const auto read_tag = get_new_tag(); // current_tag + 1; const auto sorted_list_tag = get_new_tag(); // current_tag + 2; @@ -2196,17 +2241,19 @@ void UltraCircuitConstructor::process_ROM_array(const size_t rom_id, const size_ // we have validated that all ROM reads are correctly constrained fr max_index_value((uint64_t)rom_array.state.size()); uint32_t max_index = add_variable(max_index_value); - create_big_add_gate({ - max_index, - zero_idx, - zero_idx, - zero_idx, - 1, - 0, - 0, - 0, - -max_index_value, - }); + create_big_add_gate( + { + max_index, + zero_idx, + zero_idx, + zero_idx, + 1, + 0, + 0, + 0, + -max_index_value, + }, + false); // N.B. If the above check holds, we know the sorted list begins with an index value of 0, // because the first cell is explicitly initialized using zero_idx as the index field. } @@ -2347,7 +2394,6 @@ void UltraCircuitConstructor::process_RAM_array(const size_t ram_id, const size_ 0, 0, }); - // Step 3: validate difference in timestamps is monotonically increasing. i.e. is <= maximum timestamp const size_t max_timestamp = ram_array.access_count - 1; for (auto& w : timestamp_deltas) { @@ -2368,4 +2414,857 @@ void UltraCircuitConstructor::process_RAM_arrays(const size_t gate_offset_from_p } } +// Various methods relating to circuit evaluation + +/** + * @brief Arithmetic gate-related methods + * + * @details The whole formula without alpha scaling is: + * + * q_arith * ( ( (-1/2) * (q_arith - 3) * q_m * w_1 * w_2 + q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c ) + + * (q_arith - 1)*( α * (q_arith - 2) * (w_1 + w_4 - w_1_omega + q_m) + w_4_omega) ) = 0 + * + * This formula results in several cases depending on q_arith: + * 1. q_arith == 0: Arithmetic gate is completely disabled + * + * 2. q_arith == 1: Everything in the minigate on the right is disabled. The equation is just a standard plonk equation + * with extra wires: q_m * w_1 * w_2 + q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c = 0 + * + * 3. q_arith == 2: The (w_1 + w_4 - ...) term is disabled. THe equation is: + * (1/2) * q_m * w_1 * w_2 + q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c + w_4_omega = 0 + * It allows defining w_4 at next index (w_4_omega) in terms of current wire values + * + * 4. q_arith == 3: The product of w_1 and w_2 is disabled, but a mini addition gate is enabled. α² allows us to split + * the equation into two: + * + * q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c + 2 * w_4_omega = 0 + * + * w_1 + w_4 - w_1_omega + q_m = 0 (we are reusing q_m here) + * + * 5. q_arith > 3: The product of w_1 and w_2 is scaled by (q_arith - 3), while the w_4_omega term is scaled by (q_arith + * - 1). The equation can be split into two: + * + * (q_arith - 3)* q_m * w_1 * w_ 2 + q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c + (q_arith - 1) * w_4_omega = 0 + * + * w_1 + w_4 - w_1_omega + q_m = 0 + * + * The problem that q_m is used both in both equations can be dealt with by appropriately changing selector values at + * the next gate. Then we can treat (q_arith - 1) as a simulated q_6 selector and scale q_m to handle (q_arith - 3) at + * product. + * + * Uses only the alpha challenge + * + */ + +/** + * @brief Compute the arithmetic relation/gate evaluation base on given selector and witness evaluations + * + * @details We need this function because in ultra we have committed and non-committed gates (for example RAM and ROM). + * However, we'd still like to evaluate all of them, so we can't access selectors and witness values directly. + * + * You can scroll up to look at the description of the general logic of this gate + * + * @param q_arith_value + * @param q_1_value + * @param q_2_value + * @param q_3_value + * @param q_4_value + * @param q_m_value + * @param q_c_value + * @param w_1_value + * @param w_2_value + * @param w_3_value + * @param w_4_value + * @param w_1_shifted_value + * @param w_4_shifted_value + * @param alpha_base + * @param alpha + * @return fr + */ +inline fr UltraCircuitConstructor::compute_arithmetic_identity(fr q_arith_value, + fr q_1_value, + fr q_2_value, + fr q_3_value, + fr q_4_value, + fr q_m_value, + fr q_c_value, + fr w_1_value, + fr w_2_value, + fr w_3_value, + fr w_4_value, + fr w_1_shifted_value, + fr w_4_shifted_value, + fr alpha_base, + fr alpha) const +{ + constexpr fr neg_half = fr(-2).invert(); + // The main arithmetic identity that gets activated for q_arith_value == 1 + fr arithmetic_identity = w_2_value; + arithmetic_identity *= q_m_value; + arithmetic_identity *= (q_arith_value - 3); + arithmetic_identity *= neg_half; + arithmetic_identity += q_1_value; + arithmetic_identity *= w_1_value; + arithmetic_identity += (w_2_value * q_2_value); + arithmetic_identity += (w_3_value * q_3_value); + arithmetic_identity += (w_4_value * q_4_value); + arithmetic_identity += q_c_value; + + // The additional small addition identity + fr extra_small_addition_identity = w_1_value + w_4_value - w_1_shifted_value + q_m_value; + extra_small_addition_identity *= alpha; + extra_small_addition_identity *= (q_arith_value - 2); + + // The concatenation of small addition identity + shifted w_4 value that can be enabled separately + the main + // arithemtic identity + fr final_identity = extra_small_addition_identity + w_4_shifted_value; + final_identity *= (q_arith_value - 1); + final_identity += arithmetic_identity; + final_identity *= q_arith_value; + final_identity *= alpha_base; + return final_identity; +} + +/** + * @brief General permutation sorting identity + * + * @details This identity binds together the values of witnesses on the same row (w_1, w_2, w_3, w_4) and the w_1 + * witness on the next row (w_1_shifted) so that the difference between 2 consecutive elements is in the set {0,1,2,3} + * + */ + +/** + * @brief Compute a single general permutation sorting identity + * + * @param w_1_value + * @param w_2_value + * @param w_3_value + * @param w_4_value + * @param w_1_shifted_value + * @param alpha_base + * @param alpha + * @return fr + */ +inline fr UltraCircuitConstructor::compute_genperm_sort_identity(fr q_sort_value, + fr w_1_value, + fr w_2_value, + fr w_3_value, + fr w_4_value, + fr w_1_shifted_value, + fr alpha_base, + fr alpha) const +{ + // Power of alpha to separate individual delta relations + // TODO(kesha): This is a repeated computation which can be efficiently optimized + const fr alpha_a = alpha_base; + const fr alpha_b = alpha_a * alpha; + const fr alpha_c = alpha_b * alpha; + const fr alpha_d = alpha_c * alpha; + + // (second - first)*(second - first - 1)*(second - first - 2)*(second - first - 3) + auto neighbour_difference = [](const fr first, const fr second) { + constexpr fr minus_two(-2); + constexpr fr minus_three(-3); + const fr delta = second - first; + return (delta.sqr() - delta) * (delta + minus_two) * (delta + minus_three); + }; + + return q_sort_value * (alpha_a * neighbour_difference(w_1_value, w_2_value) + + alpha_b * neighbour_difference(w_2_value, w_3_value) + + alpha_c * neighbour_difference(w_3_value, w_4_value) + + alpha_d * neighbour_difference(w_4_value, w_1_shifted_value)); +} + +/** + * @brief Elliptic curve identity gate methods implement elliptic curve point addition. The gate is enhanced to handle + * the case where one of the points is automatically scaled by the endomorphism constant β or negated + * + * + * @details The basic equation for the elliptic curve in short weierstrass form is y^2 == x^3 + a * x + b. + * + * The addition formulas are: + * λ = (y_2 - y_1) / (x_2 - x_1) + * x_3 = λ^2 - x_2 - x_1 = (y_2 - y_1)^2 / (x_2 - x_1)^2 - x_2 - x_1 = ((y_2 - y_1)^2 - (x_2 - x_1) * (x_2^2 - + * x_1^2)) / (x_2 - x_1)^2 + * + * If we assume that the points being added are distinct and not invereses of each other (so their x coordinates + * differ), then we can rephrase this equality: + * x_3 * (x_2 - x_1)^2 = ((y_2 - y_1)^2 - (x_2 - x_1) * (x_2^2 - x_1^2)) + * Let's say we want to apply the endomorphism to the (x_2, y_2) point at the same time and maybe change the sign of + * y_2: + * + * (x_2, y_2) = (β * x_2', sign * y_2') + * x_3 * (β * x_2' - x_1)^2 = ((sign * y_2' - y_1)^2 - (β * x_2' - x_1) * ((β * x_2')^2 - x_1^2)) + * + * Let's open the brackets and group the terms by β, β^2, sign: + * + * x_2'^2 * x_3 * β^2 - 2 * β * x_1 * x_2' * x_3 - x_1^2 * x_3 = sign^2 * y_2'^2 - 2 * sign * y_1 * y_2 + y_1^2 - β^3 + * * x_2'^3 + β * x_1^2 * x_2' + β^2 * x_1 * x_2'^2 - x_1^3 + * + * β^3 = 1 + * sign^2 = 1 (at least we always expect sign to be set to 1 or -1) + * + * sign * (-2 * y_1 * y_2) + β * (2 * x_1 * x_2' * x_3 +x_1^2 * x_2') + β^2 * (x_1 * x_2'^2 - x_2'^2 * x_3) + (x_1^2 * + * x_3 + y_2'^2 + y_1^2 - x_2'^3 - x_1^3) = 0 + * This is the equation computed in x_identity and scaled by α + * + * Now let's deal with the y equation: + * y_3 = λ * (x_3 - x_1) + y_1 = (y_2 - y_1) * (x_3 - x_1) / (x_2 - x_1) + y_1 = ((y_2 - y_1) * (x_3 - x_1) + y_1 * + * (x_2 - x_1)) / (x_2 - x_1) + * + * (x_2 - x_1) * y_3 = (y_2 - y_1) * (x_3 - x_1) + y_1 * (x_2 - x_1) + * + * Let's substitute (x_2, y_2) = (β * x_2', sign * y_2'): + * + * β * x_2' * y_3 - x_1 * y_3 - sign * y_2' * x_3 + y_1 * x_3 + sign * y_2' * x_1 - y_1 * x_1 - β * y_1 * x_2' + x_1 + * * y_1 = 0 + * + * Let's group: + * + * sign * (-y_2' * x_3 + y_2' * x_1) + β * (x_2' * x_3 + y_1 * x_2') + (-x_1 * y_3 + y_1 * x_3 - x_1 * y_1 + + * x_1 * y_1) = 0 + * + */ + +/** + * @brief Compute the identity of the arithmetic gate fiven all coefficients + * + * @param q_1_value 1 or -1 (the sign). Controls whether we are subtracting or adding the second point + * @param q_3_value The endomorphism coefficient β, if we are using the endomorphism here + * @param q_4_value β² if we need it + * @param w_2_value x₁ + * @param w_3_value y₁ + * @param w_1_shifted_value x₂ + * @param w_2_shifted_value y₂ + * @param w_3_shifted_value x₃ + * @param w_4_shifted_value y₃ + * @return fr + */ +inline fr UltraCircuitConstructor::compute_elliptic_identity(fr q_elliptic_value, + fr q_1_value, + fr q_3_value, + fr q_4_value, + fr w_2_value, + fr w_3_value, + fr w_1_shifted_value, + fr w_2_shifted_value, + fr w_3_shifted_value, + fr w_4_shifted_value, + fr alpha_base, + fr alpha) const +{ + // TODO(kesha): Can this be implemented more efficiently? + // It seems that Zac wanted to group the elements by selectors to use several linear terms initially, + // but in the end we are using one, so there is no reason why we can't optimize computation in another way + const fr x_1 = w_2_value; + const fr y_1 = w_3_value; + const fr x_2 = w_1_shifted_value; + const fr y_2 = w_4_shifted_value; + const fr x_3 = w_2_shifted_value; + const fr y_3 = w_3_shifted_value; + const fr q_beta = q_3_value; + const fr q_beta_sqr = q_4_value; + const fr q_sign = q_1_value; + + fr beta_term = -x_2 * x_1 * (x_3 + x_3 + x_1); // -x_1 * x_2 * (2 * x_3 + x_1) + fr beta_sqr_term = x_2.sqr(); // x_2^2 + fr leftovers = beta_sqr_term; // x_2^2 + beta_sqr_term *= (x_3 - x_1); // x_2^2 * (x_3 - x_1) + fr sign_term = y_2 * y_1; // y_1 * y_2 + sign_term += sign_term; // 2 * y_1 * y_2 + beta_term *= q_beta; // -β * x_1 * x_2 * (2 * x_3 + x_1) + beta_sqr_term *= q_beta_sqr; // β^2 * x_2^2 * (x_3 - x_1) + sign_term *= q_sign; // 2 * y_1 * y_2 * sign + leftovers *= x_2; // x_2^3 + leftovers += x_1.sqr() * (x_3 + x_1); // x_2^3 + x_1 * (x_3 + x_1) + leftovers -= (y_2.sqr() + y_1.sqr()); // x_2^3 + x_1 * (x_3 + x_1) - y_2^2 - y_1^2 + + // Can be found in class description + fr x_identity = beta_term + beta_sqr_term + sign_term + leftovers; + x_identity *= alpha_base; + + beta_term = x_2 * (y_3 + y_1) * q_beta; // β * x_2 * (y_3 + y_1) + sign_term = -y_2 * (x_1 - x_3) * q_sign; // - signt * y_2 * (x_1 - x_3) + // TODO: remove extra additions if we decide to stay with this implementation + leftovers = -x_1 * (y_3 + y_1) + y_1 * (x_1 - x_3); // -x_1 * y_3 - x_1 * y_1 + y_1 * x_1 - y_1 * x_3 + + fr y_identity = beta_term + sign_term + leftovers; + y_identity *= alpha_base * alpha; + + return q_elliptic_value * (x_identity + y_identity); +} + +/** + * @brief Plookup Auxiliary Gate Identity + * + * @details Evaluates polynomial identities associated with the following Ultra custom gates: + * * RAM/ROM read-write consistency check + * * RAM timestamp difference consistency check + * * RAM/ROM index difference consistency check + * * Bigfield product evaluation (3 in total) + * * Bigfield limb accumulation (2 in total) + * + * Multiple selectors are used to 'switch' aux gates on/off according to the following pattern: + * + * | gate type | q_aux | q_1 | q_2 | q_3 | q_4 | q_m | q_c | q_arith | + * | ---------------------------- | ----- | --- | --- | --- | --- | --- | --- | ------ | + * | Bigfield Limb Accumulation 1 | 1 | 0 | 0 | 1 | 1 | 0 | --- | 0 | + * | Bigfield Limb Accumulation 2 | 1 | 0 | 0 | 1 | 0 | 1 | --- | 0 | + * | Bigfield Product 1 | 1 | 0 | 1 | 1 | 0 | 0 | --- | 0 | + * | Bigfield Product 2 | 1 | 0 | 1 | 0 | 1 | 0 | --- | 0 | + * | Bigfield Product 3 | 1 | 0 | 1 | 0 | 0 | 1 | --- | 0 | + * | RAM/ROM access gate | 1 | 1 | 0 | 0 | 0 | 1 | --- | 0 | + * | RAM timestamp check | 1 | 1 | 0 | 0 | 1 | 0 | --- | 0 | + * | ROM consistency check | 1 | 1 | 1 | 0 | 0 | 0 | --- | 0 | + * | RAM consistency check | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | + * + * N.B. The RAM consistency check identity is degree 3. To keep the overall quotient degree at <=5, only 2 selectors can + * be used to select it. + * + * N.B.2 The q_c selector is used to store circuit-specific values in the RAM/ROM access gate + * + */ + +inline fr UltraCircuitConstructor::compute_auxilary_identity(fr q_aux_value, + fr q_arith_value, + fr q_1_value, + fr q_2_value, + fr q_3_value, + fr q_4_value, + fr q_m_value, + fr q_c_value, + fr w_1_value, + fr w_2_value, + fr w_3_value, + fr w_4_value, + fr w_1_shifted_value, + fr w_2_shifted_value, + fr w_3_shifted_value, + fr w_4_shifted_value, + fr alpha_base, + fr alpha, + fr eta) const +{ + constexpr barretenberg::fr LIMB_SIZE(uint256_t(1) << DEFAULT_NON_NATIVE_FIELD_LIMB_BITS); + // TODO(kesha): Replace with a constant defined in header + constexpr barretenberg::fr SUBLIMB_SHIFT(uint256_t(1) << 14); + + // Non-native field arithmetic gate relations + // a{a_0, ..., a_3}⋅b{b_0,...,b_3} + q{q_0,..., q_3}⋅neg_p{neg_p_0,...,neg_p_3} - r{r_0,...,r_3} = 0 mod 2²⁷² + // neg_p and limb shifts are constants, so we can use big addition gates for them. + // Activated with q_2 & (q_3 | q_4 | q_m) - first, second, third appropriately + // For native gate_1: limb_subproduct = a_1 ⋅ b_0 + a_0 ⋅ b_1 + // For native gate_2: limb_subproduct = a_0 ⋅ b_2 + a_2 ⋅ b_0 + // For native gate_3: limb_subproduct = a_2 ⋅ b_1 + a_1 ⋅ b_2 + fr limb_subproduct = w_1_value * w_2_shifted_value + w_1_shifted_value * w_2_value; + + // ( a_0 ⋅ b_3 + a_3 ⋅ b_0 - r_3 ) + fr non_native_field_gate_2 = (w_1_value * w_4_value + w_2_value * w_3_value - w_3_shifted_value); + // ( a_0 ⋅ b_3 + a_3 ⋅ b_0 - r_3 ) << 68 + non_native_field_gate_2 *= LIMB_SIZE; + // ( a_0 ⋅ b_3 + a_3 ⋅ b_0 - r_3 ) << 68 - hi_0 + non_native_field_gate_2 -= w_4_shifted_value; + // ( a_0 ⋅ b_3 + a_3 ⋅ b_0 - r_3 ) << 68 - hi_0 + a_0 ⋅ b_2 + a_2 ⋅ b_0 + non_native_field_gate_2 += limb_subproduct; + non_native_field_gate_2 *= q_4_value; + + limb_subproduct *= LIMB_SIZE; + + // ( a_1 ⋅ b_0 + a_0 ⋅ b_1 ) << 68 + ( a_0 ⋅ b_0 ) + limb_subproduct += (w_1_shifted_value * w_2_shifted_value); + fr non_native_field_gate_1 = limb_subproduct; + // ( a_1 ⋅ b_0 + a_0 ⋅ b_1 ) << 68 + ( a_0 ⋅ b_0 ) + non_native_field_gate_1 -= (w_3_value + w_4_value); + non_native_field_gate_1 *= q_3_value; + + // ( a_2 ⋅ b_1 + a_1 ⋅ b_2 ) << 68 + ( a_1 ⋅ b_1 ) + fr non_native_field_gate_3 = limb_subproduct; + // ( a_2 ⋅ b_1 + a_1 ⋅ b_2 ) << 68 + ( a_1 ⋅ b_1 ) + hi_0 + non_native_field_gate_3 += w_4_value; + // ( a_2 ⋅ b_1 + a_1 ⋅ b_2 ) << 68 + ( a_1 ⋅ b_1 ) + hi_0 - r_2 - hi_1 + non_native_field_gate_3 -= (w_3_shifted_value + w_4_shifted_value); + non_native_field_gate_3 *= q_m_value; + + // Accumulate the 3 gates and multiply by q_2 + fr non_native_field_identity = non_native_field_gate_1 + non_native_field_gate_2 + non_native_field_gate_3; + non_native_field_identity *= q_2_value; + + // Accummulator limbs. These are activated with (q_3)&( q_4 | q_m). + // The limbs are configured in such a way as to take 3 gates to process a decomposition of 2 at maximum 70-bit + // elements into 5 14-bit limbs each. Then through set permutation we can range constrain each + // + // w_4 == (w_2_shifted << 56) | (w_1_shifted << 42) | (w_3 << 28) | (w_2 << 14) | + // w_1 + fr limb_accumulator_1 = w_2_shifted_value; + limb_accumulator_1 *= SUBLIMB_SHIFT; + limb_accumulator_1 += w_1_shifted_value; + limb_accumulator_1 *= SUBLIMB_SHIFT; + limb_accumulator_1 += w_3_value; + limb_accumulator_1 *= SUBLIMB_SHIFT; + limb_accumulator_1 += w_2_value; + limb_accumulator_1 *= SUBLIMB_SHIFT; + limb_accumulator_1 += w_1_value; + limb_accumulator_1 -= w_4_value; + limb_accumulator_1 *= q_4_value; + + // w_4_shifted == (w_3_shifted << 56) | (w_2_shifted << 42) | (w_1_shifted << 28) | (w_4 << 14) | w_3 + fr limb_accumulator_2 = w_3_shifted_value; + limb_accumulator_2 *= SUBLIMB_SHIFT; + limb_accumulator_2 += w_2_shifted_value; + limb_accumulator_2 *= SUBLIMB_SHIFT; + limb_accumulator_2 += w_1_shifted_value; + limb_accumulator_2 *= SUBLIMB_SHIFT; + limb_accumulator_2 += w_4_value; + limb_accumulator_2 *= SUBLIMB_SHIFT; + limb_accumulator_2 += w_3_value; + limb_accumulator_2 -= w_4_shifted_value; + limb_accumulator_2 *= q_m_value; + + fr limb_accumulator_identity = limb_accumulator_1 + limb_accumulator_2; + limb_accumulator_identity *= q_3_value; + + /** + * MEMORY + * + * A RAM memory record contains a tuple of the following fields: + * * i: `index` of memory cell being accessed + * * t: `timestamp` of memory cell being accessed (used for RAM, set to 0 for ROM) + * * v: `value` of memory cell being accessed + * * a: `access` type of record. read: 0 = read, 1 = write + * * r: `record` of memory cell. record = access + index * eta + timestamp * eta^2 + value * eta^3 + * + * A ROM memory record contains a tuple of the following fields: + * * i: `index` of memory cell being accessed + * * v: `value1` of memory cell being accessed (ROM tables can store up to 2 values per index) + * * v2:`value2` of memory cell being accessed (ROM tables can store up to 2 values per index) + * * r: `record` of memory cell. record = index * eta + value2 * eta^2 + value1 * eta^3 + * + * When performing a read/write access, the values of i, t, v, v2, a, r are stored in the following wires + + * selectors, depending on whether the gate is a RAM read/write or a ROM read + * + * | gate type | i | v2/t | v | a | r | + * | --------- | -- | ----- | -- | -- | -- | + * | ROM | w1 | w2 | w3 | -- | w4 | + * | RAM | w1 | w2 | w3 | qc | w4 | + * + * (for accesses where `index` is a circuit constant, it is assumed the circuit will apply a copy constraint on + * `w2` to fix its value) + * + **/ + + /** + * Memory Record Check + * + * Memory record check is needed to generate a 4 ~ 1 correspondence between the record of the memory cell and all + * the other values. It allows us to use set equivalence for whole cells, since we only need to take care of + * 1 witness per cell + * + * A ROM/ROM access gate can be evaluated with the identity: + * + * qc + w1 \eta + w2 \eta^2 + w3 \eta^3 - w4 = 0 + * + * For ROM gates, qc = 0 + */ + + fr memory_record_check = w_3_value; + memory_record_check *= eta; + memory_record_check += w_2_value; + memory_record_check *= eta; + memory_record_check += w_1_value; + memory_record_check *= eta; + memory_record_check += q_c_value; + fr partial_record_check = memory_record_check; // used in RAM consistency check + memory_record_check = memory_record_check - w_4_value; + + /** + * ROM Consistency Check + * + * For every ROM read, a set equivalence check is applied between the record witnesses, and a second set of + * records that are sorted. + * + * We apply the following checks for the sorted records: + * + * 1. w1, w2, w3 correctly map to 'index', 'v1, 'v2' for a given record value at w4 + * 2. index values for adjacent records are monotonically increasing + * 3. if, at gate i, index_i == index_{i + 1}, then value1_i == value1_{i + 1} and value2_i == value2_{i + 1} + * + */ + + fr index_delta = w_1_shifted_value - w_1_value; + fr record_delta = w_4_shifted_value - w_4_value; + + // (index_delta - 1) ⋅ (index_delta) + fr index_is_monotonically_increasing = index_delta.sqr() - index_delta; + // (1 - index_delta) ⋅ (record_delta) + fr adjacent_values_match_if_adjacent_indices_match = (fr(1) - index_delta) * record_delta; + + fr ROM_consistency_check_identity = adjacent_values_match_if_adjacent_indices_match; + ROM_consistency_check_identity *= alpha; + ROM_consistency_check_identity += index_is_monotonically_increasing; + ROM_consistency_check_identity *= alpha; + // α²⋅(1 - index_delta) ⋅ record_delta + α ⋅ (index_delta - 1) ⋅ index_delta + (q_c + η ⋅ w_1 + η ⋅ w_2 + η ⋅ w_3 - + // w_4) + ROM_consistency_check_identity += memory_record_check; + + /** + * RAM Consistency Check + * + * The 'access' type of the record is extracted with the expression `w_4 - partial_record_check` + * (i.e. for an honest Prover `w1 * eta + w2 * eta^2 + w3 * eta^3 - w4 = access`. + * This is validated by requiring `access` to be boolean + * + * For two adjacent entries in the sorted list if _both_ + * A) index values match + * B) adjacent access value is 0 (i.e. next gate is a READ) + * then + * C) both values must match. + * The gate boolean check is + * (A && B) => C === !(A && B) || C === !A || !B || C + * + * N.B. it is the responsibility of the circuit writer to ensure that every RAM cell is initialized + * with a WRITE operation. + */ + fr access_type = (w_4_value - partial_record_check); // will be 0 or 1 for honest Prover + fr access_check = access_type.sqr() - access_type; // check value is 0 or 1 + + // TODO: oof nasty compute here. If we sorted in reverse order we could re-use `partial_record_check` + fr next_gate_access_type = w_3_shifted_value; + next_gate_access_type *= eta; + next_gate_access_type += w_2_shifted_value; + next_gate_access_type *= eta; + next_gate_access_type += w_1_shifted_value; + next_gate_access_type *= eta; + next_gate_access_type = w_4_shifted_value - next_gate_access_type; + + fr value_delta = w_3_shifted_value - w_3_value; + fr adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation = + (fr(1) - index_delta) * value_delta * (fr(1) - next_gate_access_type); + + // We can't apply the RAM consistency check identity on the final entry in the sorted list (the wires in the + // next gate would make the identity fail). + // We need to validate that its 'access type' bool is correct. Can't do + // with an arithmetic gate because of the `eta` factors. We need to check that the *next* gate's access type is + // correct, to cover this edge case + fr next_gate_access_type_is_boolean = next_gate_access_type.sqr() - next_gate_access_type; + + // Putting it all together... + fr RAM_consistency_check_identity = + adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation; + RAM_consistency_check_identity *= alpha; + RAM_consistency_check_identity += index_is_monotonically_increasing; + RAM_consistency_check_identity *= alpha; + RAM_consistency_check_identity += next_gate_access_type_is_boolean; + RAM_consistency_check_identity *= alpha; + RAM_consistency_check_identity += access_check; + + /** + * RAM Timestamp Consistency Check + * + * | w1 | w2 | w3 | w4 | + * | index | timestamp | timestamp_check | -- | + * + * Let delta_index = index_{i + 1} - index_{i} + * + * Iff delta_index == 0, timestamp_check = timestamp_{i + 1} - timestamp_i + * Else timestamp_check = 0 + */ + fr timestamp_delta = w_2_shifted_value - w_2_value; + fr RAM_timestamp_check_identity = (fr(1) - index_delta) * timestamp_delta - w_3_value; + + /** + * The complete RAM/ROM memory identity + * + */ + + fr memory_identity = ROM_consistency_check_identity * q_2_value; + memory_identity += RAM_timestamp_check_identity * q_4_value; + memory_identity += memory_record_check * q_m_value; + memory_identity *= q_1_value; + memory_identity += (RAM_consistency_check_identity * q_arith_value); + + fr auxiliary_identity = memory_identity + non_native_field_identity + limb_accumulator_identity; + auxiliary_identity *= q_aux_value; + auxiliary_identity *= alpha_base; + + return auxiliary_identity; +} + +/** + * @brief Check that the circuit is correct in its current state + * + * @details The method switches the circuit to the "in-the-head" version, finalizes it, checks gates, lookups and + * permutations and then switches it back from the in-the-head version, discarding the updates + * + * @return true + * @return false + */ +bool UltraCircuitConstructor::check_circuit() +{ + bool result = true; + CircuitDataBackup circuit_backup = CircuitDataBackup::store_prefinilized_state(this); + // Finalize circuit-in-the-head + + finalize_circuit(); + + // Sample randomness + const fr arithmetic_base = fr::random_element(); + const fr elliptic_base = fr::random_element(); + const fr genperm_sort_base = fr::random_element(); + const fr auxillary_base = fr::random_element(); + const fr alpha = fr::random_element(); + const fr eta = fr::random_element(); + + // We need to get all memory + std::unordered_set memory_read_record_gates; + std::unordered_set memory_write_record_gates; + for (const auto& gate_idx : memory_read_records) { + memory_read_record_gates.insert(gate_idx); + } + for (const auto& gate_idx : memory_write_records) { + memory_write_record_gates.insert(gate_idx); + } + + // A hashing implementation for quick simulation lookups + struct HashFrTuple { + const barretenberg::fr mult_const = barretenberg::fr(uint256_t(0x1337, 0x1336, 0x1335, 0x1334)); + const barretenberg::fr mc_sqr = mult_const.sqr(); + const barretenberg::fr mc_cube = mult_const * mc_sqr; + + size_t operator()( + const std::tuple& entry) const + { + return (size_t)((std::get<0>(entry) + mult_const * std::get<1>(entry) + mc_sqr * std::get<2>(entry) + + mc_cube * std::get<3>(entry)) + .reduce_once() + .data[0]); + } + }; + + // Equality checks for lookup tuples + struct EqualFrTuple { + + bool operator()( + const std::tuple& entry1, + const std::tuple& entry2) const + { + return entry1 == entry2; + } + }; + // The set of all lookup tuples that are in the tables + std::unordered_set, + HashFrTuple, + EqualFrTuple> + table_hash; + // Prepare the lookup set for use in the circuit + for (auto& table : lookup_tables) { + const fr table_index(table.table_index); + for (size_t i = 0; i < table.size; ++i) { + const auto components = + std::make_tuple(table.column_1[i], table.column_2[i], table.column_3[i], table_index); + table_hash.insert(components); + } + } + + // We use a running tag product mechanism to ensure tag correctness + // This is the product of (value + γ ⋅ tag) + fr left_tag_product = fr::one(); + // This is the product of (value + γ ⋅ tau[tag]) + fr right_tag_product = fr::one(); + // Randomness for the tag check + const fr tag_gamma = fr::random_element(); + // We need to include each variable only once + std::unordered_set encountered_variables; + + // Function to quickly update tag products and encountered variable set by index and value + auto update_tag_check_information = [&](size_t variable_index, fr value) { + size_t real_index = real_variable_index[variable_index]; + // Check to ensure that we are not including a variable twice + if (encountered_variables.contains(real_index)) { + return; + } + size_t tag_in = real_variable_tags[real_index]; + if (tag_in != DUMMY_TAG) { + size_t tag_out = tau.at((uint32_t)tag_in); + left_tag_product *= value + tag_gamma * fr(tag_in); + right_tag_product *= value + tag_gamma * fr(tag_out); + encountered_variables.insert(real_index); + } + }; + // For each gate + for (size_t i = 0; i < num_gates; i++) { + fr q_arith_value; + fr q_aux_value; + fr q_elliptic_value; + fr q_sort_value; + fr q_lookup_type_value; + fr q_1_value; + fr q_2_value; + fr q_3_value; + fr q_4_value; + fr q_m_value; + fr q_c_value; + fr w_1_value; + fr w_2_value; + fr w_3_value; + fr w_4_value; + fr w_4_index; + // Get the values of selectors and wires and update tag products along the way + q_arith_value = q_arith[i]; + q_aux_value = q_aux[i]; + q_elliptic_value = q_elliptic[i]; + q_sort_value = q_sort[i]; + q_lookup_type_value = q_lookup_type[i]; + q_1_value = q_1[i]; + q_2_value = q_2[i]; + q_3_value = q_3[i]; + q_4_value = q_4[i]; + q_m_value = q_m[i]; + q_c_value = q_c[i]; + w_1_value = get_variable(w_l[i]); + update_tag_check_information(w_l[i], w_1_value); + w_2_value = get_variable(w_r[i]); + update_tag_check_information(w_r[i], w_2_value); + w_3_value = get_variable(w_o[i]); + update_tag_check_information(w_o[i], w_3_value); + w_4_value = get_variable(w_4[i]); + // We need to wait before updating tag product for w_4 + w_4_index = w_4[i]; + + // If we are touching a gate with memory access, we need to update the value of the 4th witness + if (memory_read_record_gates.contains(i)) { + w_4_value = ((w_3_value * eta + w_2_value) * eta + w_1_value) * eta; + } + if (memory_write_record_gates.contains(i)) { + w_4_value = ((w_3_value * eta + w_2_value) * eta + w_1_value) * eta + fr::one(); + } + // Now we can update the tag product for w_4 + update_tag_check_information((uint32_t)w_4_index, w_4_value); + fr w_1_shifted_value; + fr w_2_shifted_value; + fr w_3_shifted_value; + fr w_4_shifted_value; + if (i < (num_gates - 1)) { + + w_1_shifted_value = get_variable(w_l[i + 1]); + w_2_shifted_value = get_variable(w_r[i + 1]); + w_3_shifted_value = get_variable(w_o[i + 1]); + w_4_shifted_value = get_variable(w_4[i + 1]); + } else { + w_1_shifted_value = fr::zero(); + w_2_shifted_value = fr::zero(); + w_3_shifted_value = fr::zero(); + w_4_shifted_value = fr::zero(); + } + if (memory_read_record_gates.contains(i + 1)) { + w_4_shifted_value = ((w_3_shifted_value * eta + w_2_shifted_value) * eta + w_1_shifted_value) * eta; + } + if (memory_write_record_gates.contains(i + 1)) { + w_4_shifted_value = + ((w_3_shifted_value * eta + w_2_shifted_value) * eta + w_1_shifted_value) * eta + fr::one(); + } + if (!compute_arithmetic_identity(q_arith_value, + q_1_value, + q_2_value, + q_3_value, + q_4_value, + q_m_value, + q_c_value, + w_1_value, + w_2_value, + w_3_value, + w_4_value, + w_1_shifted_value, + w_4_shifted_value, + arithmetic_base, + alpha) + .is_zero()) { +#ifndef FUZZING + info("Arithemtic identity fails at gate ", i); +#endif + result = false; + break; + } + if (!compute_auxilary_identity(q_aux_value, + q_arith_value, + q_1_value, + q_2_value, + q_3_value, + q_4_value, + q_m_value, + q_c_value, + w_1_value, + w_2_value, + w_3_value, + w_4_value, + w_1_shifted_value, + w_2_shifted_value, + w_3_shifted_value, + w_4_shifted_value, + auxillary_base, + alpha, + eta) + .is_zero()) { +#ifndef FUZZING + info("Auxilary identity fails at gate ", i); +#endif + + result = false; + break; + } + if (!compute_elliptic_identity(q_elliptic_value, + q_1_value, + q_3_value, + q_4_value, + w_2_value, + w_3_value, + w_1_shifted_value, + w_2_shifted_value, + w_3_shifted_value, + w_4_shifted_value, + elliptic_base, + alpha) + .is_zero()) { +#ifndef FUZZING + info("Elliptic identity fails at gate ", i); +#endif + result = false; + break; + } + if (!compute_genperm_sort_identity( + q_sort_value, w_1_value, w_2_value, w_3_value, w_4_value, w_1_shifted_value, genperm_sort_base, alpha) + .is_zero()) { +#ifndef FUZZING + info("Genperm sort identity fails at gate ", i); +#endif + + result = false; + break; + } + if (!q_lookup_type_value.is_zero()) { + if (!table_hash.contains(std::make_tuple(w_1_value + q_2_value * w_1_shifted_value, + w_2_value + q_m_value * w_2_shifted_value, + w_3_value + q_c_value * w_3_shifted_value, + q_3_value))) { +#ifndef FUZZING + info("Lookup fails at gate ", i); +#endif + + result = false; + break; + } + } + } + if (left_tag_product != right_tag_product) { +#ifndef FUZZING + if (result) { + info("Tag permutation failed"); + } +#endif + + result = false; + } + circuit_backup.restore_prefinilized_state(this); + return result; +} + } // namespace proof_system \ No newline at end of file diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/circuit_constructors/ultra_circuit_constructor.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/circuit_constructors/ultra_circuit_constructor.hpp index 2e4803fa449..c939163c31a 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/circuit_constructors/ultra_circuit_constructor.hpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/circuit_constructors/ultra_circuit_constructor.hpp @@ -6,208 +6,526 @@ #include "barretenberg/proof_system/flavor/flavor.hpp" #include "barretenberg/proof_system/types/merkle_hash_type.hpp" #include "barretenberg/polynomials/polynomial.hpp" -#include "barretenberg/plonk/composer/plookup_tables/types.hpp" -#include "barretenberg/plonk/composer/plookup_tables/plookup_tables.hpp" +#include "barretenberg/proof_system/plookup_tables/types.hpp" +#include "barretenberg/proof_system/plookup_tables/plookup_tables.hpp" #include "barretenberg/plonk/proof_system/types/prover_settings.hpp" #include namespace proof_system { -static constexpr ComposerType type = ComposerType::PLOOKUP; -static constexpr merkle::HashType merkle_hash_type = merkle::HashType::LOOKUP_PEDERSEN; -static constexpr size_t NUM_RESERVED_GATES = 4; // This must be >= num_roots_cut_out_of_vanishing_polynomial - // See the comment in plonk/proof_system/prover/prover.cpp - // ProverBase::compute_quotient_commitments() for why 4 exactly. -static constexpr size_t UINT_LOG2_BASE = 6; // DOCTODO: explain what this is, or rename. -// The plookup range proof requires work linear in range size, thus cannot be used directly for -// large ranges such as 2^64. For such ranges the element will be decomposed into smaller -// chuncks according to the parameter below -static constexpr size_t DEFAULT_PLOOKUP_RANGE_BITNUM = 14; -static constexpr size_t DEFAULT_PLOOKUP_RANGE_STEP_SIZE = 3; -static constexpr size_t DEFAULT_PLOOKUP_RANGE_SIZE = (1 << DEFAULT_PLOOKUP_RANGE_BITNUM) - 1; -static constexpr size_t DEFAULT_NON_NATIVE_FIELD_LIMB_BITS = 68; -static constexpr uint32_t UNINITIALIZED_MEMORY_RECORD = UINT32_MAX; -static constexpr size_t NUMBER_OF_GATES_PER_RAM_ACCESS = 2; -static constexpr size_t NUMBER_OF_ARITHMETIC_GATES_PER_RAM_ARRAY = 1; -// number of gates created per non-native field operation in process_non_native_field_multiplications -static constexpr size_t GATES_PER_NON_NATIVE_FIELD_MULTIPLICATION_ARITHMETIC = 7; - -struct non_native_field_witnesses { - // first 4 array elements = limbs - // 5th element = prime basis limb - std::array a; - std::array b; - std::array q; - std::array r; - std::array neg_modulus; - barretenberg::fr modulus; -}; +class UltraCircuitConstructor : public CircuitConstructorBase { + public: + static constexpr ComposerType type = ComposerType::PLOOKUP; + static constexpr merkle::HashType merkle_hash_type = merkle::HashType::LOOKUP_PEDERSEN; + static constexpr size_t UINT_LOG2_BASE = 6; // DOCTODO: explain what this is, or rename. + // The plookup range proof requires work linear in range size, thus cannot be used directly for + // large ranges such as 2^64. For such ranges the element will be decomposed into smaller + // chuncks according to the parameter below + static constexpr size_t DEFAULT_PLOOKUP_RANGE_BITNUM = 14; + static constexpr size_t DEFAULT_PLOOKUP_RANGE_STEP_SIZE = 3; + static constexpr size_t DEFAULT_PLOOKUP_RANGE_SIZE = (1 << DEFAULT_PLOOKUP_RANGE_BITNUM) - 1; + static constexpr size_t DEFAULT_NON_NATIVE_FIELD_LIMB_BITS = 68; + static constexpr uint32_t UNINITIALIZED_MEMORY_RECORD = UINT32_MAX; + static constexpr size_t NUMBER_OF_GATES_PER_RAM_ACCESS = 2; + static constexpr size_t NUMBER_OF_ARITHMETIC_GATES_PER_RAM_ARRAY = 1; + + struct non_native_field_witnesses { + // first 4 array elements = limbs + // 5th element = prime basis limb + std::array a; + std::array b; + std::array q; + std::array r; + std::array neg_modulus; + barretenberg::fr modulus; + }; -struct non_native_field_multiplication_cross_terms { - uint32_t lo_0_idx; - uint32_t lo_1_idx; - uint32_t hi_0_idx; - uint32_t hi_1_idx; - uint32_t hi_2_idx; - uint32_t hi_3_idx; -}; + enum AUX_SELECTORS { + NONE, + LIMB_ACCUMULATE_1, + LIMB_ACCUMULATE_2, + NON_NATIVE_FIELD_1, + NON_NATIVE_FIELD_2, + NON_NATIVE_FIELD_3, + RAM_CONSISTENCY_CHECK, + ROM_CONSISTENCY_CHECK, + RAM_TIMESTAMP_CHECK, + ROM_READ, + RAM_READ, + RAM_WRITE, + }; -/** - * @brief Used to store instructions to create non_native_field_multiplication gates. - * We want to cache these (and remove duplicates) as the stdlib code can end up multiplying the same inputs - * repeatedly. - */ -struct cached_non_native_field_multiplication { - std::array a; - std::array b; - std::array q; - std::array r; - non_native_field_multiplication_cross_terms cross_terms; - std::array neg_modulus; - - bool operator==(const cached_non_native_field_multiplication& other) const - { - bool valid = true; - for (size_t i = 0; i < 5; ++i) { - valid = valid && (a[i] == other.a[i]); - valid = valid && (b[i] == other.b[i]); - valid = valid && (q[i] == other.q[i]); - valid = valid && (r[i] == other.r[i]); + struct RangeList { + uint64_t target_range; + uint32_t range_tag; + uint32_t tau_tag; + std::vector variable_indices; + bool operator==(const RangeList& other) const noexcept + { + return target_range == other.target_range && range_tag == other.range_tag && tau_tag == other.tau_tag && + variable_indices == other.variable_indices; } - return valid; - } - bool operator<(const cached_non_native_field_multiplication& other) const + }; + + /** + * @brief A ROM memory record that can be ordered + * + * + */ + struct RomRecord { + uint32_t index_witness = 0; + uint32_t value_column1_witness = 0; + uint32_t value_column2_witness = 0; + uint32_t index = 0; + uint32_t record_witness = 0; + size_t gate_index = 0; + bool operator<(const RomRecord& other) const { return index < other.index; } + bool operator==(const RomRecord& other) const noexcept + { + return index_witness == other.index_witness && value_column1_witness == other.value_column1_witness && + value_column2_witness == other.value_column2_witness && index == other.index && + record_witness == other.record_witness && gate_index == other.gate_index; + } + }; + + /** + * @brief A RAM memory record that can be ordered + * + * + */ + struct RamRecord { + enum AccessType { + READ, + WRITE, + }; + uint32_t index_witness = 0; + uint32_t timestamp_witness = 0; + uint32_t value_witness = 0; + uint32_t index = 0; + uint32_t timestamp = 0; + AccessType access_type = AccessType::READ; // read or write? + uint32_t record_witness = 0; + size_t gate_index = 0; + bool operator<(const RamRecord& other) const + { + bool index_test = (index) < (other.index); + return index_test || (index == other.index && timestamp < other.timestamp); + } + bool operator==(const RamRecord& other) const noexcept + { + return index_witness == other.index_witness && timestamp_witness == other.timestamp_witness && + value_witness == other.value_witness && index == other.index && timestamp == other.timestamp && + access_type == other.access_type && record_witness == other.record_witness && + gate_index == other.gate_index; + } + }; + + /** + * @brief Each ram array is an instance of memory transcript. It saves values and indexes for a particular memory + * array + * + * + */ + struct RamTranscript { + // Contains the value of each index of the array + std::vector state; + + // A vector of records, each of which contains: + // + The constant witness with the index + // + The value in the memory slot + // + The actual index value + std::vector records; + + // used for RAM records, to compute the timestamp when performing a read/write + size_t access_count = 0; + // Used to check that the state hasn't changed in tests + bool operator==(const RamTranscript& other) const noexcept + { + return (state == other.state && records == other.records && access_count == other.access_count); + } + }; + + /** + * @brief Each rom array is an instance of memory transcript. It saves values and indexes for a particular memory + * array + * + * + */ + struct RomTranscript { + // Contains the value of each index of the array + std::vector> state; + + // A vector of records, each of which contains: + // + The constant witness with the index + // + The value in the memory slot + // + The actual index value + std::vector records; + + // Used to check that the state hasn't changed in tests + bool operator==(const RomTranscript& other) const noexcept + { + return (state == other.state && records == other.records); + } + }; + + inline std::vector ultra_selector_names() { - if (a < other.a) { - return true; + std::vector result{ "q_m", "q_c", "q_1", "q_2", "q_3", "q_4", + "q_arith", "q_sort", "q_elliptic", "q_aux", "table_type" }; + return result; + } + struct non_native_field_multiplication_cross_terms { + uint32_t lo_0_idx; + uint32_t lo_1_idx; + uint32_t hi_0_idx; + uint32_t hi_1_idx; + uint32_t hi_2_idx; + uint32_t hi_3_idx; + }; + /** + * @brief Used to store instructions to create non_native_field_multiplication gates. + * We want to cache these (and remove duplicates) as the stdlib code can end up multiplying the same inputs + * repeatedly. + */ + struct cached_non_native_field_multiplication { + std::array a; + std::array b; + std::array q; + std::array r; + non_native_field_multiplication_cross_terms cross_terms; + std::array neg_modulus; + + bool operator==(const cached_non_native_field_multiplication& other) const + { + bool valid = true; + for (size_t i = 0; i < 5; ++i) { + valid = valid && (a[i] == other.a[i]); + valid = valid && (b[i] == other.b[i]); + valid = valid && (q[i] == other.q[i]); + valid = valid && (r[i] == other.r[i]); + } + return valid; } - if (a == other.a) { - if (b < other.b) { + bool operator<(const cached_non_native_field_multiplication& other) const + { + if (a < other.a) { return true; } - if (b == other.b) { - if (q < other.q) { + if (a == other.a) { + if (b < other.b) { return true; } - if (q == other.q) { - if (r < other.r) { + if (b == other.b) { + if (q < other.q) { return true; } + if (q == other.q) { + if (r < other.r) { + return true; + } + } } } + return false; } - return false; - } -}; - -enum AUX_SELECTORS { - NONE, - LIMB_ACCUMULATE_1, - LIMB_ACCUMULATE_2, - NON_NATIVE_FIELD_1, - NON_NATIVE_FIELD_2, - NON_NATIVE_FIELD_3, - RAM_CONSISTENCY_CHECK, - ROM_CONSISTENCY_CHECK, - RAM_TIMESTAMP_CHECK, - ROM_READ, - RAM_READ, - RAM_WRITE, -}; - -struct RangeList { - uint64_t target_range; - uint32_t range_tag; - uint32_t tau_tag; - std::vector variable_indices; -}; - -/** - * @brief A ROM memory record that can be ordered - * - * - */ -struct RomRecord { - uint32_t index_witness = 0; - uint32_t value_column1_witness = 0; - uint32_t value_column2_witness = 0; - uint32_t index = 0; - uint32_t record_witness = 0; - size_t gate_index = 0; - bool operator<(const RomRecord& other) const { return index < other.index; } -}; - -/** - * @brief A RAM memory record that can be ordered - * - * - */ -struct RamRecord { - enum AccessType { - READ, - WRITE, }; - uint32_t index_witness = 0; - uint32_t timestamp_witness = 0; - uint32_t value_witness = 0; - uint32_t index = 0; - uint32_t timestamp = 0; - AccessType access_type = AccessType::READ; // read or write? - uint32_t record_witness = 0; - size_t gate_index = 0; - bool operator<(const RamRecord& other) const - { - bool index_test = (index) < (other.index); - return index_test || (index == other.index && timestamp < other.timestamp); - } -}; - -/** - * @brief Each ram array is an instance of memory transcript. It saves values and indexes for a particular memory - * array - * - * - */ -struct RamTranscript { - // Contains the value of each index of the array - std::vector state; - - // A vector of records, each of which contains: - // + The constant witness with the index - // + The value in the memory slot - // + The actual index value - std::vector records; - - // used for RAM records, to compute the timestamp when performing a read/write - size_t access_count = 0; -}; + /** + * @brief CircuitDataBackup is a structure we use to store all the information about the circuit that is needed to + * restore it back to a pre-finalized state + * @details In check_circuit method in UltraCircuitConstructor we want to check that the whole circuit works, but + * ultra circuits need to have ram, rom and range gates added in the end for the check to be complete as well as the + * set permutation check, so we finalize the circuit when we check it. This structure allows us to restore the + * circuit to the state before the finalization. + */ + struct CircuitDataBackup { + std::vector public_inputs; + std::vector variables; + // index of next variable in equivalence class (=REAL_VARIABLE if you're last) + std::vector next_var_index; + // index of previous variable in equivalence class (=FIRST if you're in a cycle alone) + std::vector prev_var_index; + // indices of corresponding real variables + std::vector real_variable_index; + std::vector real_variable_tags; + std::map constant_variable_indices; + std::vector w_l; + std::vector w_r; + std::vector w_o; + std::vector w_4; + std::vector q_m; + std::vector q_c; + std::vector q_1; + std::vector q_2; + std::vector q_3; + std::vector q_4; + std::vector q_arith; + std::vector q_sort; + std::vector q_elliptic; + std::vector q_aux; + std::vector q_lookup_type; + uint32_t current_tag = DUMMY_TAG; + std::map tau; + + std::vector ram_arrays; + std::vector rom_arrays; + + std::vector memory_read_records; + std::vector memory_write_records; + std::map range_lists; + + std::vector + cached_non_native_field_multiplications; + + size_t num_gates; + bool circuit_finalised = false; + /** + * @brief Stores the state of everything logic-related in the constructor. + * + * @details We need this function for tests. Specifically, to ensure that we are not changing anything in + * check_circuit + * + * @param circuit_constructor + * @return CircuitDataBackup + */ + template + static CircuitDataBackup store_full_state(const CircuitConstructor& circuit_constructor) + { + CircuitDataBackup stored_state; + stored_state.public_inputs = circuit_constructor.public_inputs; + stored_state.variables = circuit_constructor.variables; + + stored_state.next_var_index = circuit_constructor.next_var_index; + + stored_state.prev_var_index = circuit_constructor.prev_var_index; + + stored_state.real_variable_index = circuit_constructor.real_variable_index; + stored_state.real_variable_tags = circuit_constructor.real_variable_tags; + stored_state.constant_variable_indices = circuit_constructor.constant_variable_indices; + stored_state.w_l = circuit_constructor.w_l; + stored_state.w_r = circuit_constructor.w_r; + stored_state.w_o = circuit_constructor.w_o; + stored_state.w_4 = circuit_constructor.w_4; + stored_state.q_m = circuit_constructor.q_m; + stored_state.q_c = circuit_constructor.q_c; + stored_state.q_1 = circuit_constructor.q_1; + stored_state.q_2 = circuit_constructor.q_2; + stored_state.q_3 = circuit_constructor.q_3; + stored_state.q_4 = circuit_constructor.q_4; + stored_state.q_arith = circuit_constructor.q_arith; + stored_state.q_sort = circuit_constructor.q_sort; + stored_state.q_elliptic = circuit_constructor.q_elliptic; + stored_state.q_aux = circuit_constructor.q_aux; + stored_state.q_lookup_type = circuit_constructor.q_lookup_type; + stored_state.current_tag = circuit_constructor.current_tag; + stored_state.tau = circuit_constructor.tau; + + stored_state.ram_arrays = circuit_constructor.ram_arrays; + stored_state.rom_arrays = circuit_constructor.rom_arrays; + + stored_state.memory_read_records = circuit_constructor.memory_read_records; + stored_state.memory_write_records = circuit_constructor.memory_write_records; + stored_state.range_lists = circuit_constructor.range_lists; + stored_state.circuit_finalised = circuit_constructor.circuit_finalised; + stored_state.num_gates = circuit_constructor.num_gates; + stored_state.cached_non_native_field_multiplications = + circuit_constructor.cached_non_native_field_multiplications; + return stored_state; + } -/** - * @brief Each rom array is an instance of memory transcript. It saves values and indexes for a particular memory - * array - * - * - */ -struct RomTranscript { - // Contains the value of each index of the array - std::vector> state; - - // A vector of records, each of which contains: - // + The constant witness with the index - // + The value in the memory slot - // + The actual index value - std::vector records; -}; + /** + * @brief Stores the state of all members of the circuit constructor that are needed to restore the state after + * finalizing the circuit. + * + * @param circuit_constructor + * @return CircuitDataBackup + */ + template + static CircuitDataBackup store_prefinilized_state(const CircuitConstructor* circuit_constructor) + { + CircuitDataBackup stored_state; + stored_state.public_inputs = circuit_constructor->public_inputs; + stored_state.variables = circuit_constructor->variables; + + stored_state.next_var_index = circuit_constructor->next_var_index; + + stored_state.prev_var_index = circuit_constructor->prev_var_index; + + stored_state.real_variable_index = circuit_constructor->real_variable_index; + stored_state.real_variable_tags = circuit_constructor->real_variable_tags; + stored_state.constant_variable_indices = circuit_constructor->constant_variable_indices; + stored_state.current_tag = circuit_constructor->current_tag; + stored_state.tau = circuit_constructor->tau; + + stored_state.ram_arrays = circuit_constructor->ram_arrays; + stored_state.rom_arrays = circuit_constructor->rom_arrays; + + stored_state.memory_read_records = circuit_constructor->memory_read_records; + stored_state.memory_write_records = circuit_constructor->memory_write_records; + stored_state.range_lists = circuit_constructor->range_lists; + stored_state.circuit_finalised = circuit_constructor->circuit_finalised; + stored_state.num_gates = circuit_constructor->num_gates; + stored_state.cached_non_native_field_multiplications = + circuit_constructor->cached_non_native_field_multiplications; + + return stored_state; + } -inline std::vector ultra_selector_names() -{ - std::vector result{ "q_m", "q_c", "q_1", "q_2", "q_3", "q_4", - "q_arith", "q_sort", "q_elliptic", "q_aux", "table_type" }; - return result; -} + /** + * @brief Restores circuit constructor to a prefinilized state. + * + * @param circuit_constructor + * @return CircuitDataBackup + */ + template void restore_prefinilized_state(CircuitConstructor* circuit_constructor) + { + circuit_constructor->public_inputs = public_inputs; + circuit_constructor->variables = variables; + + circuit_constructor->next_var_index = next_var_index; + + circuit_constructor->prev_var_index = prev_var_index; + + circuit_constructor->real_variable_index = real_variable_index; + circuit_constructor->real_variable_tags = real_variable_tags; + circuit_constructor->constant_variable_indices = constant_variable_indices; + circuit_constructor->current_tag = current_tag; + circuit_constructor->tau = tau; + + circuit_constructor->ram_arrays = ram_arrays; + circuit_constructor->rom_arrays = rom_arrays; + + circuit_constructor->memory_read_records = memory_read_records; + circuit_constructor->memory_write_records = memory_write_records; + circuit_constructor->range_lists = range_lists; + circuit_constructor->circuit_finalised = circuit_finalised; + circuit_constructor->num_gates = num_gates; + circuit_constructor->cached_non_native_field_multiplications = cached_non_native_field_multiplications; + circuit_constructor->w_l.resize(num_gates); + circuit_constructor->w_r.resize(num_gates); + circuit_constructor->w_o.resize(num_gates); + circuit_constructor->w_4.resize(num_gates); + circuit_constructor->q_m.resize(num_gates); + circuit_constructor->q_c.resize(num_gates); + circuit_constructor->q_1.resize(num_gates); + circuit_constructor->q_2.resize(num_gates); + circuit_constructor->q_3.resize(num_gates); + circuit_constructor->q_4.resize(num_gates); + circuit_constructor->q_arith.resize(num_gates); + circuit_constructor->q_sort.resize(num_gates); + circuit_constructor->q_elliptic.resize(num_gates); + circuit_constructor->q_aux.resize(num_gates); + circuit_constructor->q_lookup_type.resize(num_gates); + } + /** + * @brief Checks that the circuit state is the same as the stored circuit's one + * + * @param circuit_constructor + * @return true + * @return false + */ + template bool is_same_state(const CircuitConstructor& circuit_constructor) + { + if (!(public_inputs == circuit_constructor.public_inputs)) { + return false; + } + if (!(variables == circuit_constructor.variables)) { + return false; + } + if (!(next_var_index == circuit_constructor.next_var_index)) { + return false; + } + if (!(prev_var_index == circuit_constructor.prev_var_index)) { + return false; + } + if (!(real_variable_index == circuit_constructor.real_variable_index)) { + return false; + } + if (!(real_variable_tags == circuit_constructor.real_variable_tags)) { + return false; + } + if (!(constant_variable_indices == circuit_constructor.constant_variable_indices)) { + return false; + } + if (!(w_l == circuit_constructor.w_l)) { + return false; + } + if (!(w_r == circuit_constructor.w_r)) { + return false; + } + if (!(w_o == circuit_constructor.w_o)) { + return false; + } + if (!(w_4 == circuit_constructor.w_4)) { + return false; + } + if (!(q_m == circuit_constructor.q_m)) { + return false; + } + if (!(q_c == circuit_constructor.q_c)) { + return false; + } + if (!(q_1 == circuit_constructor.q_1)) { + return false; + } + if (!(q_2 == circuit_constructor.q_2)) { + return false; + } + if (!(q_3 == circuit_constructor.q_3)) { + return false; + } + if (!(q_4 == circuit_constructor.q_4)) { + return false; + } + if (!(q_arith == circuit_constructor.q_arith)) { + return false; + } + if (!(q_sort == circuit_constructor.q_sort)) { + return false; + } + if (!(q_elliptic == circuit_constructor.q_elliptic)) { + return false; + } + if (!(q_aux == circuit_constructor.q_aux)) { + return false; + } + if (!(q_lookup_type == circuit_constructor.q_lookup_type)) { + return false; + } + if (!(current_tag == circuit_constructor.current_tag)) { + return false; + } + if (!(tau == circuit_constructor.tau)) { + return false; + } + if (!(ram_arrays == circuit_constructor.ram_arrays)) { + return false; + } + if (!(rom_arrays == circuit_constructor.rom_arrays)) { + return false; + } + if (!(memory_read_records == circuit_constructor.memory_read_records)) { + return false; + } + if (!(memory_write_records == circuit_constructor.memory_write_records)) { + return false; + } + if (!(range_lists == circuit_constructor.range_lists)) { + return false; + } + if (!(cached_non_native_field_multiplications == + circuit_constructor.cached_non_native_field_multiplications)) { + return false; + } + if (!(num_gates == circuit_constructor.num_gates)) { + return false; + } + if (!(circuit_finalised == circuit_constructor.circuit_finalised)) { + return false; + } + return true; + } + }; -class UltraCircuitConstructor : public CircuitConstructorBase { - public: std::vector& w_l = std::get<0>(wires); std::vector& w_r = std::get<1>(wires); std::vector& w_o = std::get<2>(wires); @@ -225,11 +543,6 @@ class UltraCircuitConstructor : public CircuitConstructorBase& q_aux = std::get<9>(selectors); std::vector& q_lookup_type = std::get<10>(selectors); - // TODO(#216)(Kesha): replace this with Honk enums after we have a verifier and no longer depend on plonk - // prover/verifier - static constexpr ComposerType type = ComposerType::STANDARD_HONK; - static constexpr size_t UINT_LOG2_BASE = 2; - // These are variables that we have used a gate on, to enforce that they are // equal to a defined value. // TODO(#216)(Adrian): Why is this not in CircuitConstructorBase @@ -529,6 +842,10 @@ class UltraCircuitConstructor : public CircuitConstructorBase + +using namespace barretenberg; + +namespace { +auto& engine = numeric::random::get_debug_engine(); +} +namespace proof_system { +using plookup::ColumnIdx; +using plookup::MultiTableId; + +TEST(ultra_circuit_constructor, create_gates_from_plookup_accumulators) +{ + UltraCircuitConstructor circuit_constructor = UltraCircuitConstructor(); + + barretenberg::fr input_value = fr::random_element(); + const fr input_hi = uint256_t(input_value).slice(126, 256); + const fr input_lo = uint256_t(input_value).slice(0, 126); + const auto input_hi_index = circuit_constructor.add_variable(input_hi); + const auto input_lo_index = circuit_constructor.add_variable(input_lo); + + const auto sequence_data_hi = plookup::get_lookup_accumulators(MultiTableId::PEDERSEN_LEFT_HI, input_hi); + const auto sequence_data_lo = plookup::get_lookup_accumulators(MultiTableId::PEDERSEN_LEFT_LO, input_lo); + + const auto lookup_witnesses_hi = circuit_constructor.create_gates_from_plookup_accumulators( + MultiTableId::PEDERSEN_LEFT_HI, sequence_data_hi, input_hi_index); + const auto lookup_witnesses_lo = circuit_constructor.create_gates_from_plookup_accumulators( + MultiTableId::PEDERSEN_LEFT_LO, sequence_data_lo, input_lo_index); + + std::vector expected_x; + std::vector expected_y; + + const size_t num_lookups_hi = + (128 + crypto::pedersen_hash::lookup::BITS_PER_TABLE) / crypto::pedersen_hash::lookup::BITS_PER_TABLE; + const size_t num_lookups_lo = 126 / crypto::pedersen_hash::lookup::BITS_PER_TABLE; + const size_t num_lookups = num_lookups_hi + num_lookups_lo; + + EXPECT_EQ(num_lookups_hi, lookup_witnesses_hi[ColumnIdx::C1].size()); + EXPECT_EQ(num_lookups_lo, lookup_witnesses_lo[ColumnIdx::C1].size()); + + std::vector expected_scalars; + expected_x.resize(num_lookups); + expected_y.resize(num_lookups); + expected_scalars.resize(num_lookups); + + { + const size_t num_rounds = (num_lookups + 1) / 2; + uint256_t bits(input_value); + + const auto mask = crypto::pedersen_hash::lookup::PEDERSEN_TABLE_SIZE - 1; + + for (size_t i = 0; i < num_rounds; ++i) { + const auto& table = crypto::pedersen_hash::lookup::get_table(i); + const size_t index = i * 2; + + uint64_t slice_a = ((bits >> (index * 9)) & mask).data[0]; + expected_x[index] = (table[(size_t)slice_a].x); + expected_y[index] = (table[(size_t)slice_a].y); + expected_scalars[index] = slice_a; + + if (i < 14) { + uint64_t slice_b = ((bits >> ((index + 1) * 9)) & mask).data[0]; + expected_x[index + 1] = (table[(size_t)slice_b].x); + expected_y[index + 1] = (table[(size_t)slice_b].y); + expected_scalars[index + 1] = slice_b; + } + } + } + + for (size_t i = num_lookups - 2; i < num_lookups; --i) { + expected_scalars[i] += (expected_scalars[i + 1] * crypto::pedersen_hash::lookup::PEDERSEN_TABLE_SIZE); + } + + size_t hi_shift = 126; + const fr hi_cumulative = circuit_constructor.get_variable(lookup_witnesses_hi[ColumnIdx::C1][0]); + for (size_t i = 0; i < num_lookups_lo; ++i) { + const fr hi_mult = fr(uint256_t(1) << hi_shift); + EXPECT_EQ(circuit_constructor.get_variable(lookup_witnesses_lo[ColumnIdx::C1][i]) + (hi_cumulative * hi_mult), + expected_scalars[i]); + EXPECT_EQ(circuit_constructor.get_variable(lookup_witnesses_lo[ColumnIdx::C2][i]), expected_x[i]); + EXPECT_EQ(circuit_constructor.get_variable(lookup_witnesses_lo[ColumnIdx::C3][i]), expected_y[i]); + hi_shift -= crypto::pedersen_hash::lookup::BITS_PER_TABLE; + } + + for (size_t i = 0; i < num_lookups_hi; ++i) { + EXPECT_EQ(circuit_constructor.get_variable(lookup_witnesses_hi[ColumnIdx::C1][i]), + expected_scalars[i + num_lookups_lo]); + EXPECT_EQ(circuit_constructor.get_variable(lookup_witnesses_hi[ColumnIdx::C2][i]), + expected_x[i + num_lookups_lo]); + EXPECT_EQ(circuit_constructor.get_variable(lookup_witnesses_hi[ColumnIdx::C3][i]), + expected_y[i + num_lookups_lo]); + } + auto saved_state = UltraCircuitConstructor::CircuitDataBackup::store_full_state(circuit_constructor); + bool result = circuit_constructor.check_circuit(); + + EXPECT_EQ(result, true); + EXPECT_TRUE(saved_state.is_same_state(circuit_constructor)); +} +TEST(ultra_circuit_constructor, base_case) +{ + UltraCircuitConstructor circuit_constructor = UltraCircuitConstructor(); + fr a = fr::one(); + circuit_constructor.add_public_variable(a); + bool result = circuit_constructor.check_circuit(); + EXPECT_EQ(result, true); +} +TEST(ultra_circuit_constructor, test_no_lookup_proof) +{ + UltraCircuitConstructor circuit_constructor = UltraCircuitConstructor(); + + for (size_t i = 0; i < 16; ++i) { + for (size_t j = 0; j < 16; ++j) { + uint64_t left = static_cast(j); + uint64_t right = static_cast(i); + uint32_t left_idx = circuit_constructor.add_variable(fr(left)); + uint32_t right_idx = circuit_constructor.add_variable(fr(right)); + uint32_t result_idx = circuit_constructor.add_variable(fr(left ^ right)); + + uint32_t add_idx = + circuit_constructor.add_variable(fr(left) + fr(right) + circuit_constructor.get_variable(result_idx)); + circuit_constructor.create_big_add_gate( + { left_idx, right_idx, result_idx, add_idx, fr(1), fr(1), fr(1), fr(-1), fr(0) }); + } + } + + bool result = circuit_constructor.check_circuit(); + EXPECT_EQ(result, true); +} + +TEST(ultra_circuit_constructor, test_elliptic_gate) +{ + typedef grumpkin::g1::affine_element affine_element; + typedef grumpkin::g1::element element; + UltraCircuitConstructor circuit_constructor = UltraCircuitConstructor(); + + affine_element p1 = crypto::generators::get_generator_data({ 0, 0 }).generator; + + affine_element p2 = crypto::generators::get_generator_data({ 0, 1 }).generator; + affine_element p3(element(p1) + element(p2)); + + uint32_t x1 = circuit_constructor.add_variable(p1.x); + uint32_t y1 = circuit_constructor.add_variable(p1.y); + uint32_t x2 = circuit_constructor.add_variable(p2.x); + uint32_t y2 = circuit_constructor.add_variable(p2.y); + uint32_t x3 = circuit_constructor.add_variable(p3.x); + uint32_t y3 = circuit_constructor.add_variable(p3.y); + + ecc_add_gate gate{ x1, y1, x2, y2, x3, y3, 1, 1 }; + circuit_constructor.create_ecc_add_gate(gate); + + grumpkin::fq beta = grumpkin::fq::cube_root_of_unity(); + affine_element p2_endo = p2; + p2_endo.x *= beta; + p3 = affine_element(element(p1) + element(p2_endo)); + x3 = circuit_constructor.add_variable(p3.x); + y3 = circuit_constructor.add_variable(p3.y); + gate = ecc_add_gate{ x1, y1, x2, y2, x3, y3, beta, 1 }; + circuit_constructor.create_ecc_add_gate(gate); + + p2_endo.x *= beta; + p3 = affine_element(element(p1) - element(p2_endo)); + x3 = circuit_constructor.add_variable(p3.x); + y3 = circuit_constructor.add_variable(p3.y); + gate = ecc_add_gate{ x1, y1, x2, y2, x3, y3, beta.sqr(), -1 }; + circuit_constructor.create_ecc_add_gate(gate); + + auto saved_state = UltraCircuitConstructor::CircuitDataBackup::store_full_state(circuit_constructor); + bool result = circuit_constructor.check_circuit(); + + EXPECT_EQ(result, true); + EXPECT_TRUE(saved_state.is_same_state(circuit_constructor)); + + gate = ecc_add_gate{ x1 + 1, y1, x2, y2, x3, y3, beta.sqr(), -1 }; + circuit_constructor.create_ecc_add_gate(gate); + + EXPECT_EQ(circuit_constructor.check_circuit(), false); +} + +TEST(ultra_circuit_constructor, non_trivial_tag_permutation) +{ + UltraCircuitConstructor circuit_constructor = UltraCircuitConstructor(); + fr a = fr::random_element(); + fr b = -a; + + auto a_idx = circuit_constructor.add_variable(a); + auto b_idx = circuit_constructor.add_variable(b); + auto c_idx = circuit_constructor.add_variable(b); + auto d_idx = circuit_constructor.add_variable(a); + + circuit_constructor.create_add_gate( + { a_idx, b_idx, circuit_constructor.zero_idx, fr::one(), fr::one(), fr::zero(), fr::zero() }); + circuit_constructor.create_add_gate( + { c_idx, d_idx, circuit_constructor.zero_idx, fr::one(), fr::one(), fr::zero(), fr::zero() }); + + circuit_constructor.create_tag(1, 2); + circuit_constructor.create_tag(2, 1); + + circuit_constructor.assign_tag(a_idx, 1); + circuit_constructor.assign_tag(b_idx, 1); + circuit_constructor.assign_tag(c_idx, 2); + circuit_constructor.assign_tag(d_idx, 2); + + auto saved_state = UltraCircuitConstructor::CircuitDataBackup::store_full_state(circuit_constructor); + bool result = circuit_constructor.check_circuit(); + + EXPECT_EQ(result, true); + EXPECT_TRUE(saved_state.is_same_state(circuit_constructor)); + + // Break the tag + circuit_constructor.real_variable_tags[circuit_constructor.real_variable_index[a_idx]] = 2; + EXPECT_EQ(circuit_constructor.check_circuit(), false); +} + +TEST(ultra_circuit_constructor, non_trivial_tag_permutation_and_cycles) +{ + UltraCircuitConstructor circuit_constructor = UltraCircuitConstructor(); + fr a = fr::random_element(); + fr c = -a; + + auto a_idx = circuit_constructor.add_variable(a); + auto b_idx = circuit_constructor.add_variable(a); + circuit_constructor.assert_equal(a_idx, b_idx); + auto c_idx = circuit_constructor.add_variable(c); + auto d_idx = circuit_constructor.add_variable(c); + circuit_constructor.assert_equal(c_idx, d_idx); + auto e_idx = circuit_constructor.add_variable(a); + auto f_idx = circuit_constructor.add_variable(a); + circuit_constructor.assert_equal(e_idx, f_idx); + auto g_idx = circuit_constructor.add_variable(c); + auto h_idx = circuit_constructor.add_variable(c); + circuit_constructor.assert_equal(g_idx, h_idx); + + circuit_constructor.create_tag(1, 2); + circuit_constructor.create_tag(2, 1); + + circuit_constructor.assign_tag(a_idx, 1); + circuit_constructor.assign_tag(c_idx, 1); + circuit_constructor.assign_tag(e_idx, 2); + circuit_constructor.assign_tag(g_idx, 2); + + circuit_constructor.create_add_gate( + { b_idx, a_idx, circuit_constructor.zero_idx, fr::one(), fr::neg_one(), fr::zero(), fr::zero() }); + circuit_constructor.create_add_gate( + { c_idx, g_idx, circuit_constructor.zero_idx, fr::one(), -fr::one(), fr::zero(), fr::zero() }); + circuit_constructor.create_add_gate( + { e_idx, f_idx, circuit_constructor.zero_idx, fr::one(), -fr::one(), fr::zero(), fr::zero() }); + + auto saved_state = UltraCircuitConstructor::CircuitDataBackup::store_full_state(circuit_constructor); + bool result = circuit_constructor.check_circuit(); + + EXPECT_EQ(result, true); + EXPECT_TRUE(saved_state.is_same_state(circuit_constructor)); + + // Break the tag + circuit_constructor.real_variable_tags[circuit_constructor.real_variable_index[a_idx]] = 2; + EXPECT_EQ(circuit_constructor.check_circuit(), false); +} +TEST(ultra_circuit_constructor, bad_tag_permutation) +{ + UltraCircuitConstructor circuit_constructor = UltraCircuitConstructor(); + fr a = fr::random_element(); + fr b = -a; + + auto a_idx = circuit_constructor.add_variable(a); + auto b_idx = circuit_constructor.add_variable(b); + auto c_idx = circuit_constructor.add_variable(b); + auto d_idx = circuit_constructor.add_variable(a + 1); + + circuit_constructor.create_add_gate({ a_idx, b_idx, circuit_constructor.zero_idx, 1, 1, 0, 0 }); + circuit_constructor.create_add_gate({ c_idx, d_idx, circuit_constructor.zero_idx, 1, 1, 0, -1 }); + + auto saved_state = UltraCircuitConstructor::CircuitDataBackup::store_full_state(circuit_constructor); + bool result = circuit_constructor.check_circuit(); + + EXPECT_EQ(result, true); + EXPECT_TRUE(saved_state.is_same_state(circuit_constructor)); + + circuit_constructor.create_tag(1, 2); + circuit_constructor.create_tag(2, 1); + + circuit_constructor.assign_tag(a_idx, 1); + circuit_constructor.assign_tag(b_idx, 1); + circuit_constructor.assign_tag(c_idx, 2); + circuit_constructor.assign_tag(d_idx, 2); + + result = circuit_constructor.check_circuit(); + EXPECT_EQ(result, false); +} + +TEST(ultra_circuit_constructor, sort_widget) +{ + UltraCircuitConstructor circuit_constructor = UltraCircuitConstructor(); + fr a = fr::one(); + fr b = fr(2); + fr c = fr(3); + fr d = fr(4); + + auto a_idx = circuit_constructor.add_variable(a); + auto b_idx = circuit_constructor.add_variable(b); + auto c_idx = circuit_constructor.add_variable(c); + auto d_idx = circuit_constructor.add_variable(d); + circuit_constructor.create_sort_constraint({ a_idx, b_idx, c_idx, d_idx }); + + bool result = circuit_constructor.check_circuit(); + + EXPECT_EQ(result, true); +} + +std::vector add_variables(UltraCircuitConstructor& circuit_constructor, std::vector variables) +{ + std::vector res; + for (size_t i = 0; i < variables.size(); i++) { + res.emplace_back(circuit_constructor.add_variable(variables[i])); + } + return res; +} +TEST(ultra_circuit_constructor, sort_with_edges_gate) +{ + + fr a = fr::one(); + fr b = fr(2); + fr c = fr(3); + fr d = fr(4); + fr e = fr(5); + fr f = fr(6); + fr g = fr(7); + fr h = fr(8); + + { + UltraCircuitConstructor circuit_constructor = UltraCircuitConstructor(); + auto a_idx = circuit_constructor.add_variable(a); + auto b_idx = circuit_constructor.add_variable(b); + auto c_idx = circuit_constructor.add_variable(c); + auto d_idx = circuit_constructor.add_variable(d); + auto e_idx = circuit_constructor.add_variable(e); + auto f_idx = circuit_constructor.add_variable(f); + auto g_idx = circuit_constructor.add_variable(g); + auto h_idx = circuit_constructor.add_variable(h); + circuit_constructor.create_sort_constraint_with_edges( + { a_idx, b_idx, c_idx, d_idx, e_idx, f_idx, g_idx, h_idx }, a, h); + bool result = circuit_constructor.check_circuit(); + + EXPECT_EQ(result, true); + } + + { + UltraCircuitConstructor circuit_constructor = UltraCircuitConstructor(); + auto a_idx = circuit_constructor.add_variable(a); + auto b_idx = circuit_constructor.add_variable(b); + auto c_idx = circuit_constructor.add_variable(c); + auto d_idx = circuit_constructor.add_variable(d); + auto e_idx = circuit_constructor.add_variable(e); + auto f_idx = circuit_constructor.add_variable(f); + auto g_idx = circuit_constructor.add_variable(g); + auto h_idx = circuit_constructor.add_variable(h); + circuit_constructor.create_sort_constraint_with_edges( + { a_idx, b_idx, c_idx, d_idx, e_idx, f_idx, g_idx, h_idx }, a, g); + + bool result = circuit_constructor.check_circuit(); + + EXPECT_EQ(result, false); + } + { + UltraCircuitConstructor circuit_constructor = UltraCircuitConstructor(); + auto a_idx = circuit_constructor.add_variable(a); + auto b_idx = circuit_constructor.add_variable(b); + auto c_idx = circuit_constructor.add_variable(c); + auto d_idx = circuit_constructor.add_variable(d); + auto e_idx = circuit_constructor.add_variable(e); + auto f_idx = circuit_constructor.add_variable(f); + auto g_idx = circuit_constructor.add_variable(g); + auto h_idx = circuit_constructor.add_variable(h); + circuit_constructor.create_sort_constraint_with_edges( + { a_idx, b_idx, c_idx, d_idx, e_idx, f_idx, g_idx, h_idx }, b, h); + + bool result = circuit_constructor.check_circuit(); + + EXPECT_EQ(result, false); + } + { + UltraCircuitConstructor circuit_constructor = UltraCircuitConstructor(); + auto a_idx = circuit_constructor.add_variable(a); + auto c_idx = circuit_constructor.add_variable(c); + auto d_idx = circuit_constructor.add_variable(d); + auto e_idx = circuit_constructor.add_variable(e); + auto f_idx = circuit_constructor.add_variable(f); + auto g_idx = circuit_constructor.add_variable(g); + auto h_idx = circuit_constructor.add_variable(h); + auto b2_idx = circuit_constructor.add_variable(fr(15)); + circuit_constructor.create_sort_constraint_with_edges( + { a_idx, b2_idx, c_idx, d_idx, e_idx, f_idx, g_idx, h_idx }, b, h); + bool result = circuit_constructor.check_circuit(); + + EXPECT_EQ(result, false); + } + { + UltraCircuitConstructor circuit_constructor = UltraCircuitConstructor(); + auto idx = add_variables(circuit_constructor, { 1, 2, 5, 6, 7, 10, 11, 13, 16, 17, 20, 22, 22, 25, + 26, 29, 29, 32, 32, 33, 35, 38, 39, 39, 42, 42, 43, 45 }); + circuit_constructor.create_sort_constraint_with_edges(idx, 1, 45); + bool result = circuit_constructor.check_circuit(); + + EXPECT_EQ(result, true); + } + { + UltraCircuitConstructor circuit_constructor = UltraCircuitConstructor(); + auto idx = add_variables(circuit_constructor, { 1, 2, 5, 6, 7, 10, 11, 13, 16, 17, 20, 22, 22, 25, + 26, 29, 29, 32, 32, 33, 35, 38, 39, 39, 42, 42, 43, 45 }); + + circuit_constructor.create_sort_constraint_with_edges(idx, 1, 29); + bool result = circuit_constructor.check_circuit(); + + EXPECT_EQ(result, false); + } +} + +TEST(ultra_circuit_constructor, range_constraint) +{ + { + UltraCircuitConstructor circuit_constructor = UltraCircuitConstructor(); + auto indices = add_variables(circuit_constructor, { 1, 2, 3, 4, 5, 6, 7, 8 }); + for (size_t i = 0; i < indices.size(); i++) { + circuit_constructor.create_new_range_constraint(indices[i], 8); + } + // auto ind = {a_idx,b_idx,c_idx,d_idx,e_idx,f_idx,g_idx,h_idx}; + circuit_constructor.create_sort_constraint(indices); + bool result = circuit_constructor.check_circuit(); + + EXPECT_EQ(result, true); + } + { + UltraCircuitConstructor circuit_constructor = UltraCircuitConstructor(); + auto indices = add_variables(circuit_constructor, { 3 }); + for (size_t i = 0; i < indices.size(); i++) { + circuit_constructor.create_new_range_constraint(indices[i], 3); + } + // auto ind = {a_idx,b_idx,c_idx,d_idx,e_idx,f_idx,g_idx,h_idx}; + circuit_constructor.create_dummy_constraints(indices); + bool result = circuit_constructor.check_circuit(); + + EXPECT_EQ(result, true); + } + { + UltraCircuitConstructor circuit_constructor = UltraCircuitConstructor(); + auto indices = add_variables(circuit_constructor, { 1, 2, 3, 4, 5, 6, 8, 25 }); + for (size_t i = 0; i < indices.size(); i++) { + circuit_constructor.create_new_range_constraint(indices[i], 8); + } + circuit_constructor.create_sort_constraint(indices); + bool result = circuit_constructor.check_circuit(); + + EXPECT_EQ(result, false); + } + { + UltraCircuitConstructor circuit_constructor = UltraCircuitConstructor(); + auto indices = add_variables(circuit_constructor, + { 1, 2, 3, 4, 5, 6, 10, 8, 15, 11, 32, 21, 42, 79, 16, 10, 3, 26, 19, 51 }); + for (size_t i = 0; i < indices.size(); i++) { + circuit_constructor.create_new_range_constraint(indices[i], 128); + } + circuit_constructor.create_dummy_constraints(indices); + bool result = circuit_constructor.check_circuit(); + + EXPECT_EQ(result, true); + } + { + UltraCircuitConstructor circuit_constructor = UltraCircuitConstructor(); + auto indices = add_variables(circuit_constructor, + { 1, 2, 3, 80, 5, 6, 29, 8, 15, 11, 32, 21, 42, 79, 16, 10, 3, 26, 13, 14 }); + for (size_t i = 0; i < indices.size(); i++) { + circuit_constructor.create_new_range_constraint(indices[i], 79); + } + circuit_constructor.create_dummy_constraints(indices); + bool result = circuit_constructor.check_circuit(); + + EXPECT_EQ(result, false); + } + { + UltraCircuitConstructor circuit_constructor = UltraCircuitConstructor(); + auto indices = add_variables(circuit_constructor, + { 1, 0, 3, 80, 5, 6, 29, 8, 15, 11, 32, 21, 42, 79, 16, 10, 3, 26, 13, 14 }); + for (size_t i = 0; i < indices.size(); i++) { + circuit_constructor.create_new_range_constraint(indices[i], 79); + } + circuit_constructor.create_dummy_constraints(indices); + bool result = circuit_constructor.check_circuit(); + + EXPECT_EQ(result, false); + } +} + +TEST(ultra_circuit_constructor, range_with_gates) +{ + + UltraCircuitConstructor circuit_constructor = UltraCircuitConstructor(); + auto idx = add_variables(circuit_constructor, { 1, 2, 3, 4, 5, 6, 7, 8 }); + for (size_t i = 0; i < idx.size(); i++) { + circuit_constructor.create_new_range_constraint(idx[i], 8); + } + + circuit_constructor.create_add_gate( + { idx[0], idx[1], circuit_constructor.zero_idx, fr::one(), fr::one(), fr::zero(), -3 }); + circuit_constructor.create_add_gate( + { idx[2], idx[3], circuit_constructor.zero_idx, fr::one(), fr::one(), fr::zero(), -7 }); + circuit_constructor.create_add_gate( + { idx[4], idx[5], circuit_constructor.zero_idx, fr::one(), fr::one(), fr::zero(), -11 }); + circuit_constructor.create_add_gate( + { idx[6], idx[7], circuit_constructor.zero_idx, fr::one(), fr::one(), fr::zero(), -15 }); + bool result = circuit_constructor.check_circuit(); + + EXPECT_EQ(result, true); +} + +TEST(ultra_circuit_constructor, range_with_gates_where_range_is_not_a_power_of_two) +{ + UltraCircuitConstructor circuit_constructor = UltraCircuitConstructor(); + auto idx = add_variables(circuit_constructor, { 1, 2, 3, 4, 5, 6, 7, 8 }); + for (size_t i = 0; i < idx.size(); i++) { + circuit_constructor.create_new_range_constraint(idx[i], 12); + } + + circuit_constructor.create_add_gate( + { idx[0], idx[1], circuit_constructor.zero_idx, fr::one(), fr::one(), fr::zero(), -3 }); + circuit_constructor.create_add_gate( + { idx[2], idx[3], circuit_constructor.zero_idx, fr::one(), fr::one(), fr::zero(), -7 }); + circuit_constructor.create_add_gate( + { idx[4], idx[5], circuit_constructor.zero_idx, fr::one(), fr::one(), fr::zero(), -11 }); + circuit_constructor.create_add_gate( + { idx[6], idx[7], circuit_constructor.zero_idx, fr::one(), fr::one(), fr::zero(), -15 }); + bool result = circuit_constructor.check_circuit(); + + EXPECT_EQ(result, true); +} + +TEST(ultra_circuit_constructor, sort_widget_complex) +{ + { + + UltraCircuitConstructor circuit_constructor = UltraCircuitConstructor(); + std::vector a = { 1, 3, 4, 7, 7, 8, 11, 14, 15, 15, 18, 19, 21, 21, 24, 25, 26, 27, 30, 32 }; + std::vector ind; + for (size_t i = 0; i < a.size(); i++) + ind.emplace_back(circuit_constructor.add_variable(a[i])); + circuit_constructor.create_sort_constraint(ind); + + bool result = circuit_constructor.check_circuit(); + EXPECT_EQ(result, true); + } + { + + UltraCircuitConstructor circuit_constructor = UltraCircuitConstructor(); + std::vector a = { 1, 3, 4, 7, 7, 8, 16, 14, 15, 15, 18, 19, 21, 21, 24, 25, 26, 27, 30, 32 }; + std::vector ind; + for (size_t i = 0; i < a.size(); i++) + ind.emplace_back(circuit_constructor.add_variable(a[i])); + circuit_constructor.create_sort_constraint(ind); + + bool result = circuit_constructor.check_circuit(); + EXPECT_EQ(result, false); + } +} +TEST(ultra_circuit_constructor, sort_widget_neg) +{ + UltraCircuitConstructor circuit_constructor = UltraCircuitConstructor(); + fr a = fr::one(); + fr b = fr(2); + fr c = fr(3); + fr d = fr(8); + + auto a_idx = circuit_constructor.add_variable(a); + auto b_idx = circuit_constructor.add_variable(b); + auto c_idx = circuit_constructor.add_variable(c); + auto d_idx = circuit_constructor.add_variable(d); + circuit_constructor.create_sort_constraint({ a_idx, b_idx, c_idx, d_idx }); + + bool result = circuit_constructor.check_circuit(); + EXPECT_EQ(result, false); +} + +TEST(ultra_circuit_constructor, composed_range_constraint) +{ + UltraCircuitConstructor circuit_constructor = UltraCircuitConstructor(); + auto c = fr::random_element(); + auto d = uint256_t(c).slice(0, 133); + auto e = fr(d); + auto a_idx = circuit_constructor.add_variable(fr(e)); + circuit_constructor.create_add_gate( + { a_idx, circuit_constructor.zero_idx, circuit_constructor.zero_idx, 1, 0, 0, -fr(e) }); + circuit_constructor.decompose_into_default_range(a_idx, 134); + + bool result = circuit_constructor.check_circuit(); + EXPECT_EQ(result, true); +} + +TEST(ultra_circuit_constructor, non_native_field_multiplication) +{ + UltraCircuitConstructor circuit_constructor = UltraCircuitConstructor(); + + fq a = fq::random_element(); + fq b = fq::random_element(); + uint256_t modulus = fq::modulus; + + uint1024_t a_big = uint512_t(uint256_t(a)); + uint1024_t b_big = uint512_t(uint256_t(b)); + uint1024_t p_big = uint512_t(uint256_t(modulus)); + + uint1024_t q_big = (a_big * b_big) / p_big; + uint1024_t r_big = (a_big * b_big) % p_big; + + uint256_t q(q_big.lo.lo); + uint256_t r(r_big.lo.lo); + + const auto split_into_limbs = [&](const uint512_t& input) { + constexpr size_t NUM_BITS = 68; + std::array limbs; + limbs[0] = input.slice(0, NUM_BITS).lo; + limbs[1] = input.slice(NUM_BITS * 1, NUM_BITS * 2).lo; + limbs[2] = input.slice(NUM_BITS * 2, NUM_BITS * 3).lo; + limbs[3] = input.slice(NUM_BITS * 3, NUM_BITS * 4).lo; + limbs[4] = fr(input.lo); + return limbs; + }; + + const auto get_limb_witness_indices = [&](const std::array& limbs) { + std::array limb_indices; + limb_indices[0] = circuit_constructor.add_variable(limbs[0]); + limb_indices[1] = circuit_constructor.add_variable(limbs[1]); + limb_indices[2] = circuit_constructor.add_variable(limbs[2]); + limb_indices[3] = circuit_constructor.add_variable(limbs[3]); + limb_indices[4] = circuit_constructor.add_variable(limbs[4]); + return limb_indices; + }; + const uint512_t BINARY_BASIS_MODULUS = uint512_t(1) << (68 * 4); + auto modulus_limbs = split_into_limbs(BINARY_BASIS_MODULUS - uint512_t(modulus)); + + const auto a_indices = get_limb_witness_indices(split_into_limbs(uint256_t(a))); + const auto b_indices = get_limb_witness_indices(split_into_limbs(uint256_t(b))); + const auto q_indices = get_limb_witness_indices(split_into_limbs(uint256_t(q))); + const auto r_indices = get_limb_witness_indices(split_into_limbs(uint256_t(r))); + + proof_system::UltraCircuitConstructor::non_native_field_witnesses inputs{ + a_indices, b_indices, q_indices, r_indices, modulus_limbs, fr(uint256_t(modulus)), + }; + const auto [lo_1_idx, hi_1_idx] = circuit_constructor.queue_non_native_field_multiplication(inputs); + circuit_constructor.range_constrain_two_limbs(lo_1_idx, hi_1_idx, 70, 70); + + auto saved_state = UltraCircuitConstructor::CircuitDataBackup::store_full_state(circuit_constructor); + bool result = circuit_constructor.check_circuit(); + + EXPECT_EQ(result, true); + EXPECT_TRUE(saved_state.is_same_state(circuit_constructor)); +} + +TEST(ultra_circuit_constructor, rom) +{ + UltraCircuitConstructor circuit_constructor = UltraCircuitConstructor(); + + uint32_t rom_values[8]{ + circuit_constructor.add_variable(fr::random_element()), circuit_constructor.add_variable(fr::random_element()), + circuit_constructor.add_variable(fr::random_element()), circuit_constructor.add_variable(fr::random_element()), + circuit_constructor.add_variable(fr::random_element()), circuit_constructor.add_variable(fr::random_element()), + circuit_constructor.add_variable(fr::random_element()), circuit_constructor.add_variable(fr::random_element()), + }; + + size_t rom_id = circuit_constructor.create_ROM_array(8); + + for (size_t i = 0; i < 8; ++i) { + circuit_constructor.set_ROM_element(rom_id, i, rom_values[i]); + } + + uint32_t a_idx = circuit_constructor.read_ROM_array(rom_id, circuit_constructor.add_variable(5)); + EXPECT_EQ(a_idx != rom_values[5], true); + uint32_t b_idx = circuit_constructor.read_ROM_array(rom_id, circuit_constructor.add_variable(4)); + uint32_t c_idx = circuit_constructor.read_ROM_array(rom_id, circuit_constructor.add_variable(1)); + + const auto d_value = circuit_constructor.get_variable(a_idx) + circuit_constructor.get_variable(b_idx) + + circuit_constructor.get_variable(c_idx); + uint32_t d_idx = circuit_constructor.add_variable(d_value); + + circuit_constructor.create_big_add_gate({ + a_idx, + b_idx, + c_idx, + d_idx, + 1, + 1, + 1, + -1, + 0, + }); + + bool result = circuit_constructor.check_circuit(); + EXPECT_EQ(result, true); +} + +TEST(ultra_circuit_constructor, ram) +{ + UltraCircuitConstructor circuit_constructor = UltraCircuitConstructor(); + + uint32_t ram_values[8]{ + circuit_constructor.add_variable(fr::random_element()), circuit_constructor.add_variable(fr::random_element()), + circuit_constructor.add_variable(fr::random_element()), circuit_constructor.add_variable(fr::random_element()), + circuit_constructor.add_variable(fr::random_element()), circuit_constructor.add_variable(fr::random_element()), + circuit_constructor.add_variable(fr::random_element()), circuit_constructor.add_variable(fr::random_element()), + }; + + size_t ram_id = circuit_constructor.create_RAM_array(8); + + for (size_t i = 0; i < 8; ++i) { + circuit_constructor.init_RAM_element(ram_id, i, ram_values[i]); + } + + uint32_t a_idx = circuit_constructor.read_RAM_array(ram_id, circuit_constructor.add_variable(5)); + EXPECT_EQ(a_idx != ram_values[5], true); + + uint32_t b_idx = circuit_constructor.read_RAM_array(ram_id, circuit_constructor.add_variable(4)); + uint32_t c_idx = circuit_constructor.read_RAM_array(ram_id, circuit_constructor.add_variable(1)); + + circuit_constructor.write_RAM_array( + ram_id, circuit_constructor.add_variable(4), circuit_constructor.add_variable(500)); + uint32_t d_idx = circuit_constructor.read_RAM_array(ram_id, circuit_constructor.add_variable(4)); + + EXPECT_EQ(circuit_constructor.get_variable(d_idx), 500); + + // ensure these vars get used in another arithmetic gate + const auto e_value = circuit_constructor.get_variable(a_idx) + circuit_constructor.get_variable(b_idx) + + circuit_constructor.get_variable(c_idx) + circuit_constructor.get_variable(d_idx); + uint32_t e_idx = circuit_constructor.add_variable(e_value); + + circuit_constructor.create_big_add_gate( + { + a_idx, + b_idx, + c_idx, + d_idx, + -1, + -1, + -1, + -1, + 0, + }, + true); + circuit_constructor.create_big_add_gate( + { + circuit_constructor.zero_idx, + circuit_constructor.zero_idx, + circuit_constructor.zero_idx, + e_idx, + 0, + 0, + 0, + 0, + 0, + }, + false); + + auto saved_state = UltraCircuitConstructor::CircuitDataBackup::store_full_state(circuit_constructor); + bool result = circuit_constructor.check_circuit(); + + EXPECT_EQ(result, true); + + EXPECT_TRUE(saved_state.is_same_state(circuit_constructor)); +} + +TEST(ultra_circuit_constructor, range_checks_on_duplicates) +{ + UltraCircuitConstructor circuit_constructor = UltraCircuitConstructor(); + + uint32_t a = circuit_constructor.add_variable(100); + uint32_t b = circuit_constructor.add_variable(100); + uint32_t c = circuit_constructor.add_variable(100); + uint32_t d = circuit_constructor.add_variable(100); + + circuit_constructor.assert_equal(a, b); + circuit_constructor.assert_equal(a, c); + circuit_constructor.assert_equal(a, d); + + circuit_constructor.create_new_range_constraint(a, 1000); + circuit_constructor.create_new_range_constraint(b, 1001); + circuit_constructor.create_new_range_constraint(c, 999); + circuit_constructor.create_new_range_constraint(d, 1000); + + circuit_constructor.create_big_add_gate( + { + a, + b, + c, + d, + 0, + 0, + 0, + 0, + 0, + }, + false); + bool result = circuit_constructor.check_circuit(); + EXPECT_EQ(result, true); +} + +TEST(ultra_circuit_constructor, check_circuit_showcase) +{ + UltraCircuitConstructor circuit_constructor = UltraCircuitConstructor(); + // check_circuit allows us to check correctness on the go + + uint32_t a = circuit_constructor.add_variable(0xdead); + uint32_t b = circuit_constructor.add_variable(0xbeef); + // Let's create 2 gates that will bind these 2 variables to be one these two values + circuit_constructor.create_poly_gate( + { a, a, circuit_constructor.zero_idx, fr(1), -fr(0xdead) - fr(0xbeef), 0, 0, fr(0xdead) * fr(0xbeef) }); + circuit_constructor.create_poly_gate( + { b, b, circuit_constructor.zero_idx, fr(1), -fr(0xdead) - fr(0xbeef), 0, 0, fr(0xdead) * fr(0xbeef) }); + + // We can check if this works + EXPECT_EQ(circuit_constructor.check_circuit(), true); + + // Now let's create a range constraint for b + circuit_constructor.create_new_range_constraint(b, 0xbeef); + + // We can check if this works + EXPECT_EQ(circuit_constructor.check_circuit(), true); + + // But what if we now assert b to be equal to a? + circuit_constructor.assert_equal(a, b, "Oh no"); + + // It fails, because a is 0xdead and it can't fit in the range constraint + EXPECT_EQ(circuit_constructor.check_circuit(), false); + + // But if we force them both back to be 0xbeef... + uint32_t c = circuit_constructor.add_variable(0xbeef); + circuit_constructor.assert_equal(c, b); + + // The circuit will magically pass again + EXPECT_EQ(circuit_constructor.check_circuit(), true); +} + +} // namespace proof_system \ No newline at end of file diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/plookup_tables/aes128.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/plookup_tables/aes128.hpp similarity index 100% rename from circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/plookup_tables/aes128.hpp rename to circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/plookup_tables/aes128.hpp diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/plookup_tables/blake2s.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/plookup_tables/blake2s.hpp similarity index 100% rename from circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/plookup_tables/blake2s.hpp rename to circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/plookup_tables/blake2s.hpp diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/plookup_tables/keccak/keccak_chi.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/plookup_tables/keccak/keccak_chi.hpp similarity index 100% rename from circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/plookup_tables/keccak/keccak_chi.hpp rename to circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/plookup_tables/keccak/keccak_chi.hpp diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/plookup_tables/keccak/keccak_input.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/plookup_tables/keccak/keccak_input.hpp similarity index 100% rename from circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/plookup_tables/keccak/keccak_input.hpp rename to circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/plookup_tables/keccak/keccak_input.hpp diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/plookup_tables/keccak/keccak_output.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/plookup_tables/keccak/keccak_output.hpp similarity index 100% rename from circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/plookup_tables/keccak/keccak_output.hpp rename to circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/plookup_tables/keccak/keccak_output.hpp diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/plookup_tables/keccak/keccak_rho.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/plookup_tables/keccak/keccak_rho.hpp similarity index 100% rename from circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/plookup_tables/keccak/keccak_rho.hpp rename to circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/plookup_tables/keccak/keccak_rho.hpp diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/plookup_tables/keccak/keccak_theta.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/plookup_tables/keccak/keccak_theta.hpp similarity index 100% rename from circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/plookup_tables/keccak/keccak_theta.hpp rename to circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/plookup_tables/keccak/keccak_theta.hpp diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/plookup_tables/non_native_group_generator.cpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/plookup_tables/non_native_group_generator.cpp similarity index 100% rename from circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/plookup_tables/non_native_group_generator.cpp rename to circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/plookup_tables/non_native_group_generator.cpp diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/plookup_tables/non_native_group_generator.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/plookup_tables/non_native_group_generator.hpp similarity index 100% rename from circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/plookup_tables/non_native_group_generator.hpp rename to circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/plookup_tables/non_native_group_generator.hpp diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/plookup_tables/pedersen.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/plookup_tables/pedersen.hpp similarity index 100% rename from circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/plookup_tables/pedersen.hpp rename to circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/plookup_tables/pedersen.hpp diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/plookup_tables/plookup_tables.cpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/plookup_tables/plookup_tables.cpp similarity index 100% rename from circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/plookup_tables/plookup_tables.cpp rename to circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/plookup_tables/plookup_tables.cpp diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/plookup_tables/plookup_tables.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/plookup_tables/plookup_tables.hpp similarity index 100% rename from circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/plookup_tables/plookup_tables.hpp rename to circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/plookup_tables/plookup_tables.hpp diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/plookup_tables/sha256.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/plookup_tables/sha256.hpp similarity index 100% rename from circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/plookup_tables/sha256.hpp rename to circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/plookup_tables/sha256.hpp diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/plookup_tables/sparse.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/plookup_tables/sparse.hpp similarity index 100% rename from circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/plookup_tables/sparse.hpp rename to circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/plookup_tables/sparse.hpp diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/plookup_tables/types.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/plookup_tables/types.hpp similarity index 100% rename from circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/plookup_tables/types.hpp rename to circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/plookup_tables/types.hpp diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/plookup_tables/uint.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/plookup_tables/uint.hpp similarity index 100% rename from circuits/cpp/barretenberg/cpp/src/barretenberg/plonk/composer/plookup_tables/uint.hpp rename to circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/plookup_tables/uint.hpp diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/commitment/pedersen/pedersen_plookup.cpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/commitment/pedersen/pedersen_plookup.cpp index 7d3bdc0950e..70b84e515e5 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/commitment/pedersen/pedersen_plookup.cpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/commitment/pedersen/pedersen_plookup.cpp @@ -3,7 +3,7 @@ #include "barretenberg/ecc/curves/grumpkin/grumpkin.hpp" #include "../../hash/pedersen/pedersen_plookup.hpp" -#include "barretenberg/plonk/composer/plookup_tables/types.hpp" +#include "barretenberg/proof_system/plookup_tables/types.hpp" #include "../../primitives/composers/composers.hpp" #include "../../primitives/plookup/plookup.hpp" diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/hash/blake2s/blake2s_plookup.cpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/hash/blake2s/blake2s_plookup.cpp index 3fad189ef28..d7f4176f7de 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/hash/blake2s/blake2s_plookup.cpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/hash/blake2s/blake2s_plookup.cpp @@ -1,8 +1,8 @@ #include "blake2s_plookup.hpp" #include "blake_util.hpp" -#include "barretenberg/plonk/composer/plookup_tables/plookup_tables.hpp" -#include "barretenberg/plonk/composer/plookup_tables/sha256.hpp" +#include "barretenberg/proof_system/plookup_tables/plookup_tables.hpp" +#include "barretenberg/proof_system/plookup_tables/sha256.hpp" #include "barretenberg/plonk/composer/ultra_composer.hpp" #include "barretenberg/stdlib/primitives/bit_array/bit_array.hpp" #include "barretenberg/stdlib/primitives/field/field.hpp" diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/hash/blake2s/blake2s_plookup.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/hash/blake2s/blake2s_plookup.hpp index a729018f9f7..008b98cd86d 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/hash/blake2s/blake2s_plookup.hpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/hash/blake2s/blake2s_plookup.hpp @@ -1,6 +1,6 @@ #pragma once #include -#include "barretenberg/plonk/composer/plookup_tables/plookup_tables.hpp" +#include "barretenberg/proof_system/plookup_tables/plookup_tables.hpp" #include "barretenberg/stdlib/primitives/uint/uint.hpp" #include "barretenberg/numeric/bitop/sparse_form.hpp" diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/hash/blake2s/blake_util.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/hash/blake2s/blake_util.hpp index fe7650bbb59..884387bedf1 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/hash/blake2s/blake_util.hpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/hash/blake2s/blake_util.hpp @@ -3,7 +3,7 @@ #include "barretenberg/plonk/composer/ultra_composer.hpp" #include "barretenberg/stdlib/primitives/uint/uint.hpp" #include "barretenberg/stdlib/primitives/byte_array/byte_array.hpp" -#include "barretenberg/plonk/composer/plookup_tables/plookup_tables.hpp" +#include "barretenberg/proof_system/plookup_tables/plookup_tables.hpp" #include "barretenberg/stdlib/primitives/plookup/plookup.hpp" namespace proof_system::plonk { diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/hash/blake3s/blake3s_plookup.cpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/hash/blake3s/blake3s_plookup.cpp index 3253483722d..ffa904e3ce2 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/hash/blake3s/blake3s_plookup.cpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/hash/blake3s/blake3s_plookup.cpp @@ -1,7 +1,7 @@ #include "blake3s_plookup.hpp" #include "../blake2s/blake_util.hpp" -#include "barretenberg/plonk/composer/plookup_tables/plookup_tables.hpp" +#include "barretenberg/proof_system/plookup_tables/plookup_tables.hpp" #include "barretenberg/plonk/composer/ultra_composer.hpp" #include "barretenberg/stdlib/primitives/bit_array/bit_array.hpp" #include "barretenberg/stdlib/primitives/field/field.hpp" diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/hash/blake3s/blake3s_plookup.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/hash/blake3s/blake3s_plookup.hpp index 789c0b96bf2..e82a9cad9de 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/hash/blake3s/blake3s_plookup.hpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/hash/blake3s/blake3s_plookup.hpp @@ -1,6 +1,6 @@ #pragma once #include -#include "barretenberg/plonk/composer/plookup_tables/plookup_tables.hpp" +#include "barretenberg/proof_system/plookup_tables/plookup_tables.hpp" #include "barretenberg/stdlib/primitives/uint/uint.hpp" #include "barretenberg/numeric/bitop/sparse_form.hpp" diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/hash/pedersen/pedersen_plookup.cpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/hash/pedersen/pedersen_plookup.cpp index d55161447a9..f451a2b5731 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/hash/pedersen/pedersen_plookup.cpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/hash/pedersen/pedersen_plookup.cpp @@ -2,7 +2,7 @@ #include "barretenberg/crypto/pedersen_hash/pedersen.hpp" #include "barretenberg/ecc/curves/grumpkin/grumpkin.hpp" -#include "barretenberg/plonk/composer/plookup_tables/types.hpp" +#include "barretenberg/proof_system/plookup_tables/types.hpp" #include "../../primitives/composers/composers.hpp" #include "../../primitives/plookup/plookup.hpp" diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/hash/sha256/sha256.test.cpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/hash/sha256/sha256.test.cpp index 8f9952da349..8589866b6d3 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/hash/sha256/sha256.test.cpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/hash/sha256/sha256.test.cpp @@ -4,7 +4,7 @@ #include "barretenberg/plonk/composer/standard_composer.hpp" #include "barretenberg/plonk/composer/turbo_composer.hpp" #include "barretenberg/plonk/composer/ultra_composer.hpp" -#include "barretenberg/plonk/composer/plookup_tables/plookup_tables.hpp" +#include "barretenberg/proof_system/plookup_tables/plookup_tables.hpp" #include "barretenberg/numeric/random/engine.hpp" #include "barretenberg/numeric/bitop/rotate.hpp" diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/hash/sha256/sha256_plookup.cpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/hash/sha256/sha256_plookup.cpp index 04a5632eabf..7b611e8ee68 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/hash/sha256/sha256_plookup.cpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/hash/sha256/sha256_plookup.cpp @@ -1,7 +1,7 @@ #include "sha256_plookup.hpp" -#include "barretenberg/plonk/composer/plookup_tables/plookup_tables.hpp" -#include "barretenberg/plonk/composer/plookup_tables/sha256.hpp" +#include "barretenberg/proof_system/plookup_tables/plookup_tables.hpp" +#include "barretenberg/proof_system/plookup_tables/sha256.hpp" #include "barretenberg/plonk/composer/ultra_composer.hpp" #include "barretenberg/stdlib/primitives/bit_array/bit_array.hpp" #include "barretenberg/stdlib/primitives/field/field.hpp" diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/hash/sha256/sha256_plookup.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/hash/sha256/sha256_plookup.hpp index b1801ad2b76..aa16ff32fd0 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/hash/sha256/sha256_plookup.hpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/hash/sha256/sha256_plookup.hpp @@ -1,6 +1,6 @@ #pragma once #include -#include "barretenberg/plonk/composer/plookup_tables/plookup_tables.hpp" +#include "barretenberg/proof_system/plookup_tables/plookup_tables.hpp" #include "barretenberg/stdlib/primitives/uint/uint.hpp" #include "barretenberg/plonk/composer/composer_base.hpp" diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield.hpp index 002fce1df12..2a1829ce225 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield.hpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield.hpp @@ -118,7 +118,7 @@ template class bigfield { // 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 uint64_t NUM_LIMB_BITS = 68; + 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); static constexpr uint1024_t DEFAULT_MAXIMUM_REMAINDER = (uint1024_t(1) << (NUM_LIMB_BITS * 3 + NUM_LAST_LIMB_BITS)) - uint1024_t(1); diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/primitives/plookup/plookup.cpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/primitives/plookup/plookup.cpp index ac251c7081f..9c60d61752f 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/primitives/plookup/plookup.cpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/primitives/plookup/plookup.cpp @@ -1,7 +1,7 @@ #include "./plookup.hpp" #include "barretenberg/plonk/composer/ultra_composer.hpp" -#include "barretenberg/plonk/composer/plookup_tables/plookup_tables.hpp" -#include "barretenberg/plonk/composer/plookup_tables/types.hpp" +#include "barretenberg/proof_system/plookup_tables/plookup_tables.hpp" +#include "barretenberg/proof_system/plookup_tables/types.hpp" namespace proof_system::plonk { class UltraComposer; diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/primitives/plookup/plookup.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/primitives/plookup/plookup.hpp index b178032b104..8e14806b4c0 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/primitives/plookup/plookup.hpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/primitives/plookup/plookup.hpp @@ -1,9 +1,9 @@ #pragma once #include #include -#include "barretenberg/plonk/composer/plookup_tables/plookup_tables.hpp" +#include "barretenberg/proof_system/plookup_tables/plookup_tables.hpp" #include "barretenberg/plonk/composer/ultra_composer.hpp" -#include "barretenberg/plonk/composer/plookup_tables/types.hpp" +#include "barretenberg/proof_system/plookup_tables/types.hpp" #include "barretenberg/stdlib/primitives/field/field.hpp" namespace proof_system::plonk {