diff --git a/cpp/src/barretenberg/plonk/composer/splitting_tmp/composer_helper/standard_plonk_composer_helper.hpp b/cpp/src/barretenberg/plonk/composer/splitting_tmp/composer_helper/standard_plonk_composer_helper.hpp index 1fdacd7aee..976290b1bc 100644 --- a/cpp/src/barretenberg/plonk/composer/splitting_tmp/composer_helper/standard_plonk_composer_helper.hpp +++ b/cpp/src/barretenberg/plonk/composer/splitting_tmp/composer_helper/standard_plonk_composer_helper.hpp @@ -34,6 +34,7 @@ template class StandardPlonkComposerHelper { : StandardPlonkComposerHelper( std::shared_ptr(new bonk::FileReferenceStringFactory("../srs_db/ignition"))) {} + StandardPlonkComposerHelper(std::shared_ptr crs_factory) : crs_factory_(std::move(crs_factory)) {} @@ -41,10 +42,12 @@ template class StandardPlonkComposerHelper { StandardPlonkComposerHelper(std::unique_ptr&& crs_factory) : crs_factory_(std::move(crs_factory)) {} + StandardPlonkComposerHelper(std::shared_ptr p_key, std::shared_ptr v_key) : circuit_proving_key(std::move(p_key)) , circuit_verification_key(std::move(v_key)) {} + StandardPlonkComposerHelper(StandardPlonkComposerHelper&& other) noexcept = default; StandardPlonkComposerHelper(const StandardPlonkComposerHelper& other) = delete; StandardPlonkComposerHelper& operator=(StandardPlonkComposerHelper&& other) noexcept = default; diff --git a/cpp/src/barretenberg/plonk/composer/splitting_tmp/composer_helper/ultra_plonk_composer_helper.cpp b/cpp/src/barretenberg/plonk/composer/splitting_tmp/composer_helper/ultra_plonk_composer_helper.cpp new file mode 100644 index 0000000000..9049ed0e0b --- /dev/null +++ b/cpp/src/barretenberg/plonk/composer/splitting_tmp/composer_helper/ultra_plonk_composer_helper.cpp @@ -0,0 +1,394 @@ +#include "ultra_plonk_composer_helper.hpp" +#include "barretenberg/plonk/proof_system/types/program_settings.hpp" +#include "barretenberg/plonk/proof_system/types/prover_settings.hpp" +#include "barretenberg/plonk/proof_system/verifier/verifier.hpp" +#include "barretenberg/proof_system/circuit_constructors/ultra_circuit_constructor.hpp" +#include "barretenberg/proof_system/composer/permutation_helper.hpp" +#include "barretenberg/plonk/proof_system/commitment_scheme/kate_commitment_scheme.hpp" + +#include +#include +#include + +namespace plonk { + +/** + * @brief Computes `this.witness`, which is basiclly a set of polynomials mapped-to by strings. + * + * Note: this doesn't actually compute the _entire_ witness. Things missing: randomness for blinding both the wires and + * sorted `s` poly, lookup rows of the wire witnesses, the values of `z_lookup`, `z`. These are all calculated + * elsewhere. + */ +template +void UltraPlonkComposerHelper::compute_witness(CircuitConstructor& circuit_constructor) +{ + if (computed_witness) { + return; + } + + size_t tables_size = 0; + size_t lookups_size = 0; + for (const auto& table : circuit_constructor.lookup_tables) { + tables_size += table.size; + lookups_size += table.lookup_gates.size(); + } + + 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); + + // 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 + // next). + for (size_t i = filled_gates; i < total_num_gates; ++i) { + circuit_constructor.w_l.emplace_back(circuit_constructor.zero_idx); + circuit_constructor.w_r.emplace_back(circuit_constructor.zero_idx); + circuit_constructor.w_o.emplace_back(circuit_constructor.zero_idx); + circuit_constructor.w_4.emplace_back(circuit_constructor.zero_idx); + } + + // 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); + + for (size_t j = 0; j < program_width; ++j) { + std::string index = std::to_string(j + 1); + circuit_proving_key->polynomial_store.put("w_" + index + "_lagrange", + std::move(wire_polynomial_evaluations[j])); + } + + polynomial s_1(subgroup_size); + polynomial s_2(subgroup_size); + polynomial s_3(subgroup_size); + polynomial s_4(subgroup_size); + polynomial z_lookup(subgroup_size + 1); // Only instantiated in this function; nothing assigned. + + // Save space for adding random scalars in the s polynomial later. + // The subtracted 1 allows us to insert a `1` at the end, to ensure the evaluations (and hence coefficients) aren't + // all 0. + // See ComposerBase::compute_proving_key_base for further explanation, as a similar trick is done there. + size_t count = subgroup_size - tables_size - lookups_size - s_randomness - 1; + for (size_t i = 0; i < count; ++i) { + s_1[i] = 0; + s_2[i] = 0; + s_3[i] = 0; + s_4[i] = 0; + } + + for (auto& table : circuit_constructor.lookup_tables) { + const fr table_index(table.table_index); + auto& lookup_gates = table.lookup_gates; + for (size_t i = 0; i < table.size; ++i) { + if (table.use_twin_keys) { + lookup_gates.push_back({ + { + table.column_1[i].from_montgomery_form().data[0], + table.column_2[i].from_montgomery_form().data[0], + }, + { + table.column_3[i], + 0, + }, + }); + } else { + lookup_gates.push_back({ + { + table.column_1[i].from_montgomery_form().data[0], + 0, + }, + { + table.column_2[i], + table.column_3[i], + }, + }); + } + } + +#ifdef NO_TBB + std::sort(lookup_gates.begin(), lookup_gates.end()); +#else + std::sort(std::execution::par_unseq, lookup_gates.begin(), lookup_gates.end()); +#endif + + for (const auto& entry : lookup_gates) { + const auto components = entry.to_sorted_list_components(table.use_twin_keys); + s_1[count] = components[0]; + s_2[count] = components[1]; + s_3[count] = components[2]; + s_4[count] = table_index; + ++count; + } + } + + // Initialise the `s_randomness` positions in the s polynomials with 0. + // These will be the positions where we will be adding random scalars to add zero knowledge + // to plookup (search for `Blinding` in plonk/proof_system/widgets/random_widgets/plookup_widget_impl.hpp + // ProverPlookupWidget::compute_sorted_list_polynomial()) + for (size_t i = 0; i < s_randomness; ++i) { + s_1[count] = 0; + s_2[count] = 0; + s_3[count] = 0; + s_4[count] = 0; + ++count; + } + + circuit_proving_key->polynomial_store.put("s_1_lagrange", std::move(s_1)); + circuit_proving_key->polynomial_store.put("s_2_lagrange", std::move(s_2)); + circuit_proving_key->polynomial_store.put("s_3_lagrange", std::move(s_3)); + circuit_proving_key->polynomial_store.put("s_4_lagrange", std::move(s_4)); + + computed_witness = true; +} + +template +UltraProver UltraPlonkComposerHelper::create_prover(CircuitConstructor& circuit_constructor) +{ + finalize_circuit(circuit_constructor); + + compute_proving_key(circuit_constructor); + compute_witness(circuit_constructor); + + UltraProver output_state(circuit_proving_key, create_manifest(circuit_constructor.public_inputs.size())); + + std::unique_ptr> permutation_widget = + std::make_unique>(circuit_proving_key.get()); + + std::unique_ptr> plookup_widget = + std::make_unique>(circuit_proving_key.get()); + + std::unique_ptr> arithmetic_widget = + std::make_unique>(circuit_proving_key.get()); + + std::unique_ptr> sort_widget = + std::make_unique>(circuit_proving_key.get()); + + std::unique_ptr> elliptic_widget = + std::make_unique>(circuit_proving_key.get()); + + std::unique_ptr> auxiliary_widget = + std::make_unique>(circuit_proving_key.get()); + + output_state.random_widgets.emplace_back(std::move(permutation_widget)); + output_state.random_widgets.emplace_back(std::move(plookup_widget)); + + output_state.transition_widgets.emplace_back(std::move(arithmetic_widget)); + output_state.transition_widgets.emplace_back(std::move(sort_widget)); + output_state.transition_widgets.emplace_back(std::move(elliptic_widget)); + output_state.transition_widgets.emplace_back(std::move(auxiliary_widget)); + + std::unique_ptr> kate_commitment_scheme = + std::make_unique>(); + + output_state.commitment_scheme = std::move(kate_commitment_scheme); + + return output_state; +} + +/** + * Create verifier: compute verification key, + * initialize verifier with it and an initial manifest and initialize commitment_scheme. + * + * @return The verifier. + * */ +// TODO(Cody): This should go away altogether. +template +plonk::UltraVerifier UltraPlonkComposerHelper::create_verifier( + const CircuitConstructor& circuit_constructor) +{ + auto verification_key = compute_verification_key(circuit_constructor); + + plonk::UltraVerifier output_state(circuit_verification_key, + create_manifest(circuit_constructor.public_inputs.size())); + + std::unique_ptr> kate_commitment_scheme = + std::make_unique>(); + + output_state.commitment_scheme = std::move(kate_commitment_scheme); + + return output_state; +} + +template +std::shared_ptr UltraPlonkComposerHelper::compute_proving_key( + const CircuitConstructor& circuit_constructor) +{ + if (circuit_proving_key) { + return circuit_proving_key; + } + + size_t tables_size = 0; + size_t lookups_size = 0; + for (const auto& table : circuit_constructor.lookup_tables) { + tables_size += table.size; + lookups_size += table.lookup_gates.size(); + } + + const size_t minimum_circuit_size = tables_size + lookups_size; + const size_t num_randomized_gates = NUM_RANDOMIZED_GATES; + // Initialize circuit_proving_key + // TODO(#229)(Kesha): replace composer types. + circuit_proving_key = initialize_proving_key(circuit_constructor, + crs_factory_.get(), + minimum_circuit_size, + num_randomized_gates, + plonk::ComposerType::PLOOKUP); + + construct_lagrange_selector_forms(circuit_constructor, circuit_proving_key.get()); + + enforce_nonzero_polynomial_selectors(circuit_constructor, circuit_proving_key.get()); + + compute_monomial_and_coset_selector_forms(circuit_proving_key.get(), ultra_selector_properties()); + + bonk::compute_plonk_generalized_sigma_permutations(circuit_constructor, + circuit_proving_key.get()); + + const size_t subgroup_size = circuit_proving_key->circuit_size; + + polynomial poly_q_table_column_1(subgroup_size); + polynomial poly_q_table_column_2(subgroup_size); + polynomial poly_q_table_column_3(subgroup_size); + polynomial poly_q_table_column_4(subgroup_size); + + size_t offset = subgroup_size - tables_size - s_randomness - 1; + + // Create lookup selector polynomials which interpolate each table column. + // Our selector polys always need to interpolate the full subgroup size, so here we offset so as to + // put the table column's values at the end. (The first gates are for non-lookup constraints). + // [0, ..., 0, ...table, 0, 0, 0, x] + // ^^^^^^^^^ ^^^^^^^^ ^^^^^^^ ^nonzero to ensure uniqueness and to avoid infinity commitments + // | table randomness + // ignored, as used for regular constraints and padding to the next power of 2. + + for (size_t i = 0; i < offset; ++i) { + poly_q_table_column_1[i] = 0; + poly_q_table_column_2[i] = 0; + poly_q_table_column_3[i] = 0; + poly_q_table_column_4[i] = 0; + } + + for (const auto& table : circuit_constructor.lookup_tables) { + const fr table_index(table.table_index); + + for (size_t i = 0; i < table.size; ++i) { + poly_q_table_column_1[offset] = table.column_1[i]; + poly_q_table_column_2[offset] = table.column_2[i]; + poly_q_table_column_3[offset] = table.column_3[i]; + poly_q_table_column_4[offset] = table_index; + ++offset; + } + } + + // Initialise the last `s_randomness` positions in table polynomials with 0. We don't need to actually randomise + // the table polynomials. + for (size_t i = 0; i < s_randomness; ++i) { + poly_q_table_column_1[offset] = 0; + poly_q_table_column_2[offset] = 0; + poly_q_table_column_3[offset] = 0; + poly_q_table_column_4[offset] = 0; + ++offset; + } + + // // In the case of using UltraPlonkComposer for a circuit which does _not_ make use of any lookup tables, all four + // // table columns would be all zeros. This would result in these polys' commitments all being the point at + // infinity + // // (which is bad because our point arithmetic assumes we'll never operate on the point at infinity). To avoid + // this, + // // we set the last evaluation of each poly to be nonzero. The last `num_roots_cut_out_of_vanishing_poly = 4` + // // evaluations are ignored by constraint checks; we arbitrarily choose the very-last evaluation to be nonzero. + // See + // // ComposerBase::compute_proving_key_base for further explanation, as a similar trick is done there. We could + // // have chosen `1` for each such evaluation here, but that would have resulted in identical commitments for + // // all four columns. We don't want to have equal commitments, because biggroup operations assume no points are + // // equal, so if we tried to verify an ultra proof in a circuit, the biggroup operations would fail. To combat + // // this, we just choose distinct values: + ASSERT(offset == subgroup_size - 1); + auto unique_last_value = + get_num_selectors() + 1; // Note: in compute_proving_key_base, moments earlier, each selector + // vector was given a unique last value from 1..num_selectors. So we + // avoid those values and continue the count, to ensure uniqueness. + poly_q_table_column_1[subgroup_size - 1] = unique_last_value; + poly_q_table_column_2[subgroup_size - 1] = ++unique_last_value; + poly_q_table_column_3[subgroup_size - 1] = ++unique_last_value; + poly_q_table_column_4[subgroup_size - 1] = ++unique_last_value; + + add_table_column_selector_poly_to_proving_key(poly_q_table_column_1, "table_value_1"); + add_table_column_selector_poly_to_proving_key(poly_q_table_column_2, "table_value_2"); + add_table_column_selector_poly_to_proving_key(poly_q_table_column_3, "table_value_3"); + add_table_column_selector_poly_to_proving_key(poly_q_table_column_4, "table_value_4"); + + // Instantiate z_lookup and s polynomials in the proving key (no values assigned yet). + // Note: might be better to add these polys to cache only after they've been computed, as is convention + // TODO(luke): Don't put empty polynomials in the store, just add these where they're computed + polynomial z_lookup_fft(subgroup_size * 4); + polynomial s_fft(subgroup_size * 4); + circuit_proving_key->polynomial_store.put("z_lookup_fft", std::move(z_lookup_fft)); + circuit_proving_key->polynomial_store.put("s_fft", std::move(s_fft)); + + // Copy memory read/write record data into proving key. Prover needs to know which gates contain a read/write + // 'record' witness on the 4th wire. This wire value can only be fully computed once the first 3 wire polynomials + // have been committed to. The 4th wire on these gates will be a random linear combination of the first 3 wires, + // using the plookup challenge `eta` + std::copy(circuit_constructor.memory_read_records.begin(), + circuit_constructor.memory_read_records.end(), + std::back_inserter(circuit_proving_key->memory_read_records)); + std::copy(circuit_constructor.memory_write_records.begin(), + circuit_constructor.memory_write_records.end(), + std::back_inserter(circuit_proving_key->memory_write_records)); + + circuit_proving_key->recursive_proof_public_input_indices = + std::vector(recursive_proof_public_input_indices.begin(), recursive_proof_public_input_indices.end()); + + circuit_proving_key->contains_recursive_proof = contains_recursive_proof; + + return circuit_proving_key; +} + +/** + * Compute verification key consisting of selector precommitments. + * + * @return Pointer to created circuit verification key. + * */ +template +std::shared_ptr UltraPlonkComposerHelper::compute_verification_key( + const CircuitConstructor& circuit_constructor) +{ + if (circuit_verification_key) { + return circuit_verification_key; + } + + if (!circuit_proving_key) { + compute_proving_key(circuit_constructor); + } + circuit_verification_key = compute_verification_key_common(circuit_proving_key, crs_factory_->get_verifier_crs()); + + circuit_verification_key->composer_type = type; // Invariably plookup for this class. + + // See `add_recusrive_proof()` for how this recursive data is assigned. + circuit_verification_key->recursive_proof_public_input_indices = + std::vector(recursive_proof_public_input_indices.begin(), recursive_proof_public_input_indices.end()); + + circuit_verification_key->contains_recursive_proof = contains_recursive_proof; + + return circuit_verification_key; +} + +template +void UltraPlonkComposerHelper::add_table_column_selector_poly_to_proving_key( + polynomial& selector_poly_lagrange_form, const std::string& tag) +{ + polynomial selector_poly_lagrange_form_copy(selector_poly_lagrange_form, circuit_proving_key->small_domain.size); + + selector_poly_lagrange_form.ifft(circuit_proving_key->small_domain); + auto& selector_poly_coeff_form = selector_poly_lagrange_form; + + polynomial selector_poly_coset_form(selector_poly_coeff_form, circuit_proving_key->circuit_size * 4); + selector_poly_coset_form.coset_fft(circuit_proving_key->large_domain); + + circuit_proving_key->polynomial_store.put(tag, std::move(selector_poly_coeff_form)); + circuit_proving_key->polynomial_store.put(tag + "_lagrange", std::move(selector_poly_lagrange_form_copy)); + circuit_proving_key->polynomial_store.put(tag + "_fft", std::move(selector_poly_coset_form)); +} + +template class UltraPlonkComposerHelper; +} // namespace plonk diff --git a/cpp/src/barretenberg/plonk/composer/splitting_tmp/composer_helper/ultra_plonk_composer_helper.hpp b/cpp/src/barretenberg/plonk/composer/splitting_tmp/composer_helper/ultra_plonk_composer_helper.hpp new file mode 100644 index 0000000000..45b218460d --- /dev/null +++ b/cpp/src/barretenberg/plonk/composer/splitting_tmp/composer_helper/ultra_plonk_composer_helper.hpp @@ -0,0 +1,211 @@ +#pragma once + +#include "barretenberg/proof_system/composer/composer_helper_lib.hpp" +#include "barretenberg/srs/reference_string/file_reference_string.hpp" +#include "barretenberg/proof_system/proving_key/proving_key.hpp" +#include "barretenberg/plonk/proof_system/prover/prover.hpp" +#include "barretenberg/plonk/proof_system/verifier/verifier.hpp" + +#include +#include + +namespace plonk { +// TODO(Kesha): change initializations to specify this parameter +// Cody: What does this mean? +template class UltraPlonkComposerHelper { + public: + // TODO(luke): In the split composers, NUM_RANDOMIZED_GATES has replaced NUM_RESERVED_GATES (in some places) to + // determine the next-power-of-2 circuit size. (There are some places in this composer that still use + // 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 program_width = CircuitConstructor::program_width; + std::shared_ptr circuit_proving_key; + std::shared_ptr circuit_verification_key; + // TODO(#218)(kesha): we need to put this into the commitment key, so that the composer doesn't have to handle srs + // at all + std::shared_ptr crs_factory_; + + std::vector recursive_proof_public_input_indices; + bool contains_recursive_proof = false; + bool computed_witness = false; + + // This variable controls the amount with which the lookup table and witness values need to be shifted + // above to make room for adding randomness into the permutation and witness polynomials in the plookup widget. + // This must be (num_roots_cut_out_of_the_vanishing_polynomial - 1), since the variable num_roots_cut_out_of_ + // vanishing_polynomial cannot be trivially fetched here, I am directly setting this to 4 - 1 = 3. + static constexpr size_t s_randomness = 3; + + explicit UltraPlonkComposerHelper(std::shared_ptr crs_factory) + : crs_factory_(std::move(crs_factory)) + {} + + UltraPlonkComposerHelper(std::shared_ptr p_key, std::shared_ptr v_key) + : circuit_proving_key(std::move(p_key)) + , circuit_verification_key(std::move(v_key)) + {} + + UltraPlonkComposerHelper(UltraPlonkComposerHelper&& other) noexcept = default; + UltraPlonkComposerHelper(UltraPlonkComposerHelper const& other) noexcept = default; + UltraPlonkComposerHelper& operator=(UltraPlonkComposerHelper&& other) noexcept = default; + UltraPlonkComposerHelper& operator=(UltraPlonkComposerHelper const& other) noexcept = default; + ~UltraPlonkComposerHelper() = default; + + std::vector ultra_selector_properties() + { + // When reading and writing the proving key from a buffer we must precompute the Lagrange form of certain + // selector polynomials. In order to avoid a new selector type and definitions in the polynomial manifest, we + // can instead store the Lagrange forms of all the selector polynomials. + // + // This workaround increases the memory footprint of the prover, and is a possible place of improvement in the + // future. Below is the previous state showing where the Lagrange form is necessary for a selector: + // { "q_m", true }, { "q_c", true }, { "q_1", true }, { "q_2", true }, + // { "q_3", true }, { "q_4", false }, { "q_arith", false }, { "q_sort", false }, + // { "q_elliptic", false }, { "q_aux", false }, { "table_type", true }, + std::vector result{ + { "q_m", true }, { "q_c", true }, { "q_1", true }, { "q_2", true }, + { "q_3", true }, { "q_4", true }, { "q_arith", true }, { "q_sort", true }, + { "q_elliptic", true }, { "q_aux", true }, { "table_type", true }, + }; + return result; + } + + [[nodiscard]] size_t get_num_selectors() { return ultra_selector_properties().size(); } + + void finalize_circuit(CircuitConstructor& circuit_constructor) { circuit_constructor.finalize_circuit(); }; + + std::shared_ptr compute_proving_key(const CircuitConstructor& circuit_constructor); + std::shared_ptr compute_verification_key(const CircuitConstructor& circuit_constructor); + + void compute_witness(CircuitConstructor& circuit_constructor); + + UltraProver create_prover(CircuitConstructor& circuit_constructor); + UltraVerifier create_verifier(const CircuitConstructor& circuit_constructor); + + void add_table_column_selector_poly_to_proving_key(polynomial& small, const std::string& tag); + + /** + * @brief Create a manifest object + * + * @note UltraPlonk manifest does not use linearisation trick + * @param num_public_inputs + * @return transcript::Manifest + */ + static transcript::Manifest create_manifest(const size_t num_public_inputs) + { + // add public inputs.... + constexpr size_t g1_size = 64; + constexpr size_t fr_size = 32; + const size_t public_input_size = fr_size * num_public_inputs; + transcript::Manifest output = transcript::Manifest( + + { transcript::Manifest::RoundManifest( + { // { name, num_bytes, derived_by_verifier } + { "circuit_size", 4, true }, + { "public_input_size", 4, true } }, + "init", // challenge_name + 1 // num_challenges_in + ), + + transcript::Manifest::RoundManifest( + { // { name, num_bytes, derived_by_verifier } + { "public_inputs", public_input_size, false }, + { "W_1", g1_size, false }, + { "W_2", g1_size, false }, + { "W_3", g1_size, false } }, + "eta", // challenge_name + 1 // num_challenges_in + ), + + transcript::Manifest::RoundManifest( + { // { name, num_bytes, derived_by_verifier } + { "W_4", g1_size, false }, + { "S", g1_size, false } }, + "beta", // challenge_name + 2 // num_challenges_in + ), + + transcript::Manifest::RoundManifest( + { // { name, num_bytes, derived_by_verifier } + { "Z_PERM", g1_size, false }, + { "Z_LOOKUP", g1_size, false } }, + "alpha", // challenge_name + 1 // num_challenges_in + ), + + transcript::Manifest::RoundManifest( + { // { name, num_bytes, derived_by_verifier } + { "T_1", g1_size, false }, + { "T_2", g1_size, false }, + { "T_3", g1_size, false }, + { "T_4", g1_size, false } }, + "z", // challenge_name + 1 // num_challenges_in + ), + + // N.B. THE SHFITED EVALS (_omega) MUST HAVE THE SAME CHALLENGE INDEX AS THE NON SHIFTED VALUES + transcript::Manifest::RoundManifest( + { + // { name, num_bytes, derived_by_verifier, challenge_map_index } + { "t", fr_size, true, -1 }, // * + { "w_1", fr_size, false, 0 }, + { "w_2", fr_size, false, 1 }, + { "w_3", fr_size, false, 2 }, + { "w_4", fr_size, false, 3 }, + { "s", fr_size, false, 4 }, + { "z_perm", fr_size, false, 5 }, // * + { "z_lookup", fr_size, false, 6 }, + { "q_1", fr_size, false, 7 }, + { "q_2", fr_size, false, 8 }, + { "q_3", fr_size, false, 9 }, + { "q_4", fr_size, false, 10 }, + { "q_m", fr_size, false, 11 }, + { "q_c", fr_size, false, 12 }, + { "q_arith", fr_size, false, 13 }, + { "q_sort", fr_size, false, 14 }, // * + { "q_elliptic", fr_size, false, 15 }, // * + { "q_aux", fr_size, false, 16 }, + { "sigma_1", fr_size, false, 17 }, + { "sigma_2", fr_size, false, 18 }, + { "sigma_3", fr_size, false, 19 }, + { "sigma_4", fr_size, false, 20 }, + { "table_value_1", fr_size, false, 21 }, + { "table_value_2", fr_size, false, 22 }, + { "table_value_3", fr_size, false, 23 }, + { "table_value_4", fr_size, false, 24 }, + { "table_type", fr_size, false, 25 }, + { "id_1", fr_size, false, 26 }, + { "id_2", fr_size, false, 27 }, + { "id_3", fr_size, false, 28 }, + { "id_4", fr_size, false, 29 }, + { "w_1_omega", fr_size, false, 0 }, + { "w_2_omega", fr_size, false, 1 }, + { "w_3_omega", fr_size, false, 2 }, + { "w_4_omega", fr_size, false, 3 }, + { "s_omega", fr_size, false, 4 }, + { "z_perm_omega", fr_size, false, 5 }, + { "z_lookup_omega", fr_size, false, 6 }, + { "table_value_1_omega", fr_size, false, 21 }, + { "table_value_2_omega", fr_size, false, 22 }, + { "table_value_3_omega", fr_size, false, 23 }, + { "table_value_4_omega", fr_size, false, 24 }, + }, + "nu", // challenge_name + ULTRA_MANIFEST_SIZE, // num_challenges_in + true // map_challenges_in + ), + + transcript::Manifest::RoundManifest( + { // { name, num_bytes, derived_by_verifier, challenge_map_index } + { "PI_Z", g1_size, false }, + { "PI_Z_OMEGA", g1_size, false } }, + "separator", // challenge_name + 3 // num_challenges_in + ) }); + + return output; + } +}; + +} // namespace plonk diff --git a/cpp/src/barretenberg/plonk/composer/splitting_tmp/ultra_plonk_composer.hpp b/cpp/src/barretenberg/plonk/composer/splitting_tmp/ultra_plonk_composer.hpp new file mode 100644 index 0000000000..4c448818ad --- /dev/null +++ b/cpp/src/barretenberg/plonk/composer/splitting_tmp/ultra_plonk_composer.hpp @@ -0,0 +1,476 @@ +#pragma once +#include "barretenberg/plonk/composer/composer_base.hpp" +#include "barretenberg/plonk/composer/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/plonk/composer/splitting_tmp/composer_helper/ultra_plonk_composer_helper.hpp" +#include + +using namespace bonk; + +namespace plonk { + +class UltraPlonkComposer { + + public: + // An instantiation of the circuit constructor that only depends on arithmetization, not on the proof system + UltraCircuitConstructor circuit_constructor; + // Composer helper contains all proof-related material that is separate from circuit creation such as: + // 1) Proving and verification keys + // 2) CRS + // 3) Converting variables to witness vectors/polynomials + UltraPlonkComposerHelper composer_helper; + size_t& num_gates; + + UltraPlonkComposer() + : UltraPlonkComposer("../srs_db/ignition", 0){}; + + UltraPlonkComposer(std::string const& crs_path, const size_t size_hint) + : UltraPlonkComposer(std::unique_ptr(new FileReferenceStringFactory(crs_path)), + size_hint){}; + + UltraPlonkComposer(std::shared_ptr const& crs_factory, const size_t size_hint) + : circuit_constructor(size_hint) + , composer_helper(crs_factory) + , num_gates(circuit_constructor.num_gates){}; + + UltraPlonkComposer(std::shared_ptr const& p_key, + std::shared_ptr const& v_key, + size_t size_hint = 0); + UltraPlonkComposer(UltraPlonkComposer&& other) = default; + UltraPlonkComposer& operator=(UltraPlonkComposer&& other) = delete; + ~UltraPlonkComposer() = default; + + uint32_t get_zero_idx() { return circuit_constructor.zero_idx; } + + uint32_t add_variable(const barretenberg::fr& in) { return circuit_constructor.add_variable(in); } + + barretenberg::fr get_variable(const uint32_t index) const { return circuit_constructor.get_variable(index); } + + void finalize_circuit() { circuit_constructor.finalize_circuit(); }; + + UltraProver create_prover() { return composer_helper.create_prover(circuit_constructor); }; + UltraVerifier create_verifier() { return composer_helper.create_verifier(circuit_constructor); }; + + // UltraToStandardProver create_ultra_to_standard_prover(); + // UltraToStandardVerifier create_ultra_to_standard_verifier(); + + void create_add_gate(const add_triple& in) { circuit_constructor.create_add_gate(in); } + + void create_big_add_gate(const add_quad& in, const bool use_next_gate_w_4 = false) + { + circuit_constructor.create_big_add_gate(in, use_next_gate_w_4); + }; + + // void create_big_add_gate_with_bit_extraction(const add_quad& in); + // void create_big_mul_gate(const mul_quad& in); + // void create_balanced_add_gate(const add_quad& in); + + // void create_mul_gate(const mul_triple& in) override; + // void create_bool_gate(const uint32_t a) override; + // void create_poly_gate(const poly_triple& in) override; + void create_ecc_add_gate(const ecc_add_gate& in) { circuit_constructor.create_ecc_add_gate(in); }; + + // void fix_witness(const uint32_t witness_index, const barretenberg::fr& witness_value); + + // void add_recursive_proof(const std::vector& proof_output_witness_indices) + // { + // if (contains_recursive_proof) { + // failure("added recursive proof when one already exists"); + // } + // contains_recursive_proof = true; + + // for (const auto& idx : proof_output_witness_indices) { + // set_public_input(idx); + // recursive_proof_public_input_indices.push_back((uint32_t)(public_inputs.size() - 1)); + // } + // } + + void create_new_range_constraint(const uint32_t variable_index, + const uint64_t target_range, + std::string const msg = "create_new_range_constraint") + { + circuit_constructor.create_new_range_constraint(variable_index, target_range, msg); + }; + // void create_range_constraint(const uint32_t variable_index, const size_t num_bits, std::string const& msg) + // { + // if (num_bits <= DEFAULT_PLOOKUP_RANGE_BITNUM) { + // /** + // * N.B. if `variable_index` is not used in any arithmetic constraints, this will create an unsatisfiable + // * circuit! + // * this range constraint will increase the size of the 'sorted set' of range-constrained integers + // by 1. + // * The 'non-sorted set' of range-constrained integers is a subset of the wire indices of all + // arithmetic + // * gates. No arithemtic gate => size imbalance between sorted and non-sorted sets. Checking for this + // * and throwing an error would require a refactor of the Composer to catelog all 'orphan' variables + // not + // * assigned to gates. + // **/ + // create_new_range_constraint(variable_index, 1ULL << num_bits, msg); + // } else { + // decompose_into_default_range(variable_index, num_bits, DEFAULT_PLOOKUP_RANGE_BITNUM, msg); + // } + // } + + // accumulator_triple create_logic_constraint(const uint32_t a, + // const uint32_t b, + // const size_t num_bits, + // bool is_xor_gate); + // accumulator_triple create_and_constraint(const uint32_t a, const uint32_t b, const size_t num_bits); + // accumulator_triple create_xor_constraint(const uint32_t a, const uint32_t b, const size_t num_bits); + + // uint32_t put_constant_variable(const barretenberg::fr& variable); + + // size_t get_num_constant_gates() const override { return 0; } + + // /** + // * @brief Get the final number of gates in a circuit, which consists of the sum of: + // * 1) Current number number of actual gates + // * 2) Number of public inputs, as we'll need to add a gate for each of them + // * 3) Number of Rom array-associated gates + // * 4) NUmber of range-list associated gates + // * + // * + // * @param count return arument, number of existing gates + // * @param rangecount return argument, extra gates due to range checks + // * @param romcount return argument, extra gates due to rom reads + // * @param ramcount return argument, extra gates due to ram read/writes + // */ + // void get_num_gates_split_into_components(size_t& count, + // size_t& rangecount, + // size_t& romcount, + // size_t& ramcount) const + // { + // count = num_gates; + // // each ROM gate adds +1 extra gate due to the rom reads being copied to a sorted list set + // for (size_t i = 0; i < rom_arrays.size(); ++i) { + // for (size_t j = 0; j < rom_arrays[i].state.size(); ++j) { + // if (rom_arrays[i].state[j][0] == UNINITIALIZED_MEMORY_RECORD) { + // romcount += 2; + // } + // } + // romcount += (rom_arrays[i].records.size()); + // romcount += 1; // we add an addition gate after procesing a rom array + // } + + // constexpr size_t gate_width = ultra_settings::program_width; + // // each RAM gate adds +2 extra gates due to the ram reads being copied to a sorted list set, + // // as well as an extra gate to validate timestamps + // std::vector ram_timestamps; + // std::vector ram_range_sizes; + // std::vector ram_range_exists; + // for (size_t i = 0; i < ram_arrays.size(); ++i) { + // for (size_t j = 0; j < ram_arrays[i].state.size(); ++j) { + // if (ram_arrays[i].state[j] == UNINITIALIZED_MEMORY_RECORD) { + // ramcount += NUMBER_OF_GATES_PER_RAM_ACCESS; + // } + // } + // ramcount += (ram_arrays[i].records.size() * NUMBER_OF_GATES_PER_RAM_ACCESS); + // ramcount += NUMBER_OF_ARITHMETIC_GATES_PER_RAM_ARRAY; // we add an addition gate after procesing a ram + // array + + // // there will be 'max_timestamp' number of range checks, need to calculate. + // const auto max_timestamp = ram_arrays[i].access_count - 1; + + // // if a range check of length `max_timestamp` already exists, we are double counting. + // // We record `ram_timestamps` to detect and correct for this error when we process range lists. + // ram_timestamps.push_back(max_timestamp); + // size_t padding = (gate_width - (max_timestamp % gate_width)) % gate_width; + // if (max_timestamp == gate_width) + // padding += gate_width; + // const size_t ram_range_check_list_size = max_timestamp + padding; + + // size_t ram_range_check_gate_count = (ram_range_check_list_size / gate_width); + // ram_range_check_gate_count += 1; // we need to add 1 extra addition gates for every distinct range list + + // ram_range_sizes.push_back(ram_range_check_gate_count); + // ram_range_exists.push_back(false); + // // rangecount += ram_range_check_gate_count; + // } + // for (const auto& list : range_lists) { + // auto list_size = list.second.variable_indices.size(); + // size_t padding = (gate_width - (list.second.variable_indices.size() % gate_width)) % gate_width; + // if (list.second.variable_indices.size() == gate_width) + // padding += gate_width; + // list_size += padding; + + // for (size_t i = 0; i < ram_timestamps.size(); ++i) { + // if (list.second.target_range == ram_timestamps[i]) { + // ram_range_exists[i] = true; + // } + // } + // rangecount += (list_size / gate_width); + // rangecount += 1; // we need to add 1 extra addition gates for every distinct range list + // } + // // update rangecount to include the ram range checks the composer will eventually be creating + // for (size_t i = 0; i < ram_range_sizes.size(); ++i) { + // if (!ram_range_exists[i]) { + // rangecount += ram_range_sizes[i]; + // } + // } + // } + + // /** + // * @brief Get the final number of gates in a circuit, which consists of the sum of: + // * 1) Current number number of actual gates + // * 2) Number of public inputs, as we'll need to add a gate for each of them + // * 3) Number of Rom array-associated gates + // * 4) NUmber of range-list associated gates + // * + // * @return size_t + // */ + // virtual size_t get_num_gates() const override + // { + // // if circuit finalised already added extra gates + // if (circuit_finalised) { + // return num_gates; + // } + // size_t count = 0; + // size_t rangecount = 0; + // size_t romcount = 0; + // size_t ramcount = 0; + // get_num_gates_split_into_components(count, rangecount, romcount, ramcount); + // return count + romcount + ramcount + rangecount; + // } + + // virtual void print_num_gates() const override + // { + // size_t count = 0; + // size_t rangecount = 0; + // size_t romcount = 0; + // size_t ramcount = 0; + + // get_num_gates_split_into_components(count, rangecount, romcount, ramcount); + + // size_t total = count + romcount + ramcount + rangecount; + // std::cout << "gates = " << total << " (arith " << count << ", rom " << romcount << ", ram " << ramcount + // << ", range " << rangecount << "), pubinp = " << public_inputs.size() << std::endl; + // } + + void assert_equal(const uint32_t a_variable_idx, + const uint32_t b_variable_idx, + std::string const& msg = "assert_equal") + { + circuit_constructor.assert_equal(a_variable_idx, b_variable_idx, msg); + } + + // void assert_equal_constant(const uint32_t a_idx, + // const barretenberg::fr& b, + // std::string const& msg = "assert equal constant") + // { + // if (variables[a_idx] != b && !failed()) { + // failure(msg); + // } + // auto b_idx = put_constant_variable(b); + // assert_equal(a_idx, b_idx, msg); + // } + + // /** + // * Plookup Methods + // **/ + // void add_table_column_selector_poly_to_proving_key(polynomial& small, const std::string& tag); + // void initialize_precomputed_table( + // const plookup::BasicTableId id, + // bool (*generator)(std::vector&, + // std::vector&, + // std::vector&), + // std::array (*get_values_from_key)(const std::array)); + + // plookup::BasicTable& get_table(const plookup::BasicTableId id); + // plookup::MultiTable& create_table(const plookup::MultiTableId id); + + plookup::ReadData create_gates_from_plookup_accumulators( + const plookup::MultiTableId& id, + const plookup::ReadData& read_values, + const uint32_t key_a_index, + std::optional key_b_index = std::nullopt) + { + return circuit_constructor.create_gates_from_plookup_accumulators(id, read_values, key_a_index, key_b_index); + }; + + // /** + // * Generalized Permutation Methods + // **/ + 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, + std::string const& msg = "decompose_into_default_range") + { + return circuit_constructor.decompose_into_default_range(variable_index, num_bits, target_range_bitnum, msg); + }; + // std::vector decompose_into_default_range_better_for_oddlimbnum( + // const uint32_t variable_index, + // const size_t num_bits, + // std::string const& msg = "decompose_into_default_range_better_for_oddlimbnum"); + void create_dummy_constraints(const std::vector& variable_index) + { + circuit_constructor.create_dummy_constraints(variable_index); + }; + void create_sort_constraint(const std::vector& variable_index) + { + circuit_constructor.create_sort_constraint(variable_index); + }; + void create_sort_constraint_with_edges(const std::vector& variable_index, + const barretenberg::fr& start, + const barretenberg::fr& end) + { + circuit_constructor.create_sort_constraint_with_edges(variable_index, start, end); + }; + + void assign_tag(const uint32_t variable_index, const uint32_t tag) + { + circuit_constructor.assign_tag(variable_index, tag); + } + + // void assign_tag(const uint32_t variable_index, const uint32_t tag) + // { + // ASSERT(tag <= current_tag); + // ASSERT(real_variable_tags[real_variable_index[variable_index]] == DUMMY_TAG); + // real_variable_tags[real_variable_index[variable_index]] = tag; + // } + + uint32_t create_tag(const uint32_t tag_index, const uint32_t tau_index) + { + return circuit_constructor.create_tag(tag_index, tau_index); + } + + // uint32_t get_new_tag() + // { + // current_tag++; + // return current_tag; + // } + + // RangeList create_range_list(const uint64_t target_range); + // void process_range_list(const RangeList& list); + // void process_range_lists(); + + // /** + // * Custom Gate Selectors + // **/ + // void apply_aux_selectors(const AUX_SELECTORS type); + + // /** + // * 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) + { + 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 evaluate_non_native_field_multiplication( + const bonk::non_native_field_witnesses& input, const bool range_constrain_quotient_and_remainder = true) + { + return circuit_constructor.evaluate_non_native_field_multiplication(input, + range_constrain_quotient_and_remainder); + }; + // std::array evaluate_partial_non_native_field_multiplication(const non_native_field_witnesses& + // input); typedef std::pair scaled_witness; typedef std::tuple add_simple; std::array evaluate_non_native_field_subtraction( + // add_simple limb0, + // add_simple limb1, + // add_simple limb2, + // add_simple limb3, + // std::tuple limbp); + // std::array evaluate_non_native_field_addition(add_simple limb0, + // add_simple limb1, + // add_simple limb2, + // add_simple limb3, + // std::tuple + // limbp); + + // /** + // * Memory + // **/ + + size_t create_RAM_array(const size_t array_size) { return circuit_constructor.create_RAM_array(array_size); }; + size_t create_ROM_array(const size_t array_size) { return circuit_constructor.create_ROM_array(array_size); }; + + void set_ROM_element(const size_t rom_id, const size_t index_value, const uint32_t value_witness) + { + circuit_constructor.set_ROM_element(rom_id, index_value, value_witness); + }; + // void set_ROM_element_pair(const size_t rom_id, + // const size_t index_value, + // const std::array& value_witnesses); + uint32_t read_ROM_array(const size_t rom_id, const uint32_t index_witness) + { + return circuit_constructor.read_ROM_array(rom_id, index_witness); + }; + // std::array read_ROM_array_pair(const size_t rom_id, const uint32_t index_witness); + // void create_ROM_gate(RomRecord& record); + // void create_sorted_ROM_gate(RomRecord& record); + // void process_ROM_array(const size_t rom_id, const size_t gate_offset_from_public_inputs); + // void process_ROM_arrays(const size_t gate_offset_from_public_inputs); + + // void create_RAM_gate(RamRecord& record); + // void create_sorted_RAM_gate(RamRecord& record); + // void create_final_sorted_RAM_gate(RamRecord& record, const size_t ram_array_size); + + // size_t create_RAM_array(const size_t array_size); + void init_RAM_element(const size_t ram_id, const size_t index_value, const uint32_t value_witness) + { + circuit_constructor.init_RAM_element(ram_id, index_value, value_witness); + }; + uint32_t read_RAM_array(const size_t ram_id, const uint32_t index_witness) + { + return circuit_constructor.read_RAM_array(ram_id, index_witness); + }; + void write_RAM_array(const size_t ram_id, const uint32_t index_witness, const uint32_t value_witness) + { + circuit_constructor.write_RAM_array(ram_id, index_witness, value_witness); + }; + // void process_RAM_array(const size_t ram_id, const size_t gate_offset_from_public_inputs); + // void process_RAM_arrays(const size_t gate_offset_from_public_inputs); + + // /** + // * Member Variables + // **/ + + // uint32_t zero_idx = 0; + bool circuit_finalised = false; + + // // This variable controls the amount with which the lookup table and witness values need to be shifted + // // above to make room for adding randomness into the permutation and witness polynomials in the plookup widget. + // // This must be (num_roots_cut_out_of_the_vanishing_polynomial - 1), since the variable num_roots_cut_out_of_ + // // vanishing_polynomial cannot be trivially fetched here, I am directly setting this to 4 - 1 = 3. + // static constexpr size_t s_randomness = 3; + + // // these are variables that we have used a gate on, to enforce that they are equal to a defined value + // std::map constant_variable_indices; + + // std::vector lookup_tables; + // std::vector lookup_multi_tables; + // std::map range_lists; // DOCTODO: explain this. + + // /** + // * @brief Each entry in ram_arrays represents an independent RAM table. + // * RamTranscript tracks the current table state, + // * as well as the 'records' produced by each read and write operation. + // * Used in `compute_proving_key` to generate consistency check gates required to validate the RAM read/write + // history + // */ + // std::vector ram_arrays; + + // /** + // * @brief Each entry in ram_arrays represents an independent ROM table. + // * RomTranscript tracks the current table state, + // * as well as the 'records' produced by each read operation. + // * Used in `compute_proving_key` to generate consistency check gates required to validate the ROM read history + // */ + // std::vector rom_arrays; + + // // Stores gate index of ROM and RAM reads (required by proving key) + // std::vector memory_read_records; + // // Stores gate index of RAM writes (required by proving key) + // std::vector memory_write_records; + + // std::vector recursive_proof_public_input_indices; + // bool contains_recursive_proof = false; +}; +} // namespace plonk diff --git a/cpp/src/barretenberg/plonk/composer/splitting_tmp/ultra_plonk_composer.test.cpp b/cpp/src/barretenberg/plonk/composer/splitting_tmp/ultra_plonk_composer.test.cpp new file mode 100644 index 0000000000..416cee213e --- /dev/null +++ b/cpp/src/barretenberg/plonk/composer/splitting_tmp/ultra_plonk_composer.test.cpp @@ -0,0 +1,917 @@ +#include "barretenberg/common/log.hpp" +#include "ultra_plonk_composer.hpp" +#include "barretenberg/crypto/pedersen/pedersen.hpp" +#include "barretenberg/plonk/composer/ultra_composer.hpp" // temporary +#include +#include +#include +#include "barretenberg/numeric/bitop/get_msb.hpp" +#include "barretenberg/numeric/uintx/uintx.hpp" + +using namespace barretenberg; +using namespace bonk; + +namespace plonk { + +namespace { +auto& engine = numeric::random::get_debug_engine(); +} + +using plookup::ColumnIdx; +using plookup::MultiTableId; + +std::vector add_variables(UltraPlonkComposer& composer, std::vector variables) +{ + std::vector res; + for (size_t i = 0; i < variables.size(); i++) { + res.emplace_back(composer.add_variable(variables[i])); + } + return res; +} + +// TODO(luke): TEMPORARY: this test is useful for debugging descrepencies between old composer and new one +TEST(ultra_plonk_composer_splitting_tmp, debug_composer_discrepencies) +{ + UltraPlonkComposer composer_new = UltraPlonkComposer(); + UltraComposer composer = UltraComposer(); + + 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 = composer.add_variable(fr(left)); + uint32_t right_idx = composer.add_variable(fr(right)); + uint32_t result_idx = composer.add_variable(fr(left ^ right)); + + uint32_t add_idx = composer.add_variable(fr(left) + fr(right) + composer.get_variable(result_idx)); + composer.create_big_add_gate( + { left_idx, right_idx, result_idx, add_idx, fr(1), fr(1), fr(1), fr(-1), fr(0) }); + } + } + + 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 = composer_new.add_variable(fr(left)); + uint32_t right_idx = composer_new.add_variable(fr(right)); + uint32_t result_idx = composer_new.add_variable(fr(left ^ right)); + + uint32_t add_idx = composer_new.add_variable(fr(left) + fr(right) + composer_new.get_variable(result_idx)); + composer_new.create_big_add_gate( + { left_idx, right_idx, result_idx, add_idx, fr(1), fr(1), fr(1), fr(-1), fr(0) }); + } + } + + EXPECT_EQ(composer.num_gates, composer_new.num_gates); + + auto prover = composer.create_prover(); + auto prover_new = composer_new.create_prover(); + + for (const auto& [key, poly] : prover.key->polynomial_store) { + if (prover_new.key->polynomial_store.contains(key)) { + // info(key); + EXPECT_EQ(prover.key->polynomial_store.get(key), prover_new.key->polynomial_store.get(key)); + } + } + + auto verifier = composer.create_verifier(); + auto verifier_new = composer_new.create_verifier(); + + for (const auto& [key, poly] : verifier.key->commitments) { + // info(key); + EXPECT_EQ(verifier.key->commitments[key], verifier_new.key->commitments[key]); + } + + auto proof = prover.construct_proof(); + auto proof_new = prover_new.construct_proof(); + + bool result_new = verifier_new.verify_proof(proof_new); // instance, prover.reference_string.SRS_T2); + EXPECT_EQ(result_new, true); +} + +TEST(ultra_plonk_composer_splitting_tmp, create_gates_from_plookup_accumulators) +{ + auto composer = UltraPlonkComposer(); + + 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 = composer.add_variable(input_hi); + const auto input_lo_index = composer.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 = composer.create_gates_from_plookup_accumulators( + MultiTableId::PEDERSEN_LEFT_HI, sequence_data_hi, input_hi_index); + const auto lookup_witnesses_lo = composer.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::lookup::BITS_PER_TABLE) / crypto::pedersen::lookup::BITS_PER_TABLE; + const size_t num_lookups_lo = 126 / crypto::pedersen::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::lookup::PEDERSEN_TABLE_SIZE - 1; + + for (size_t i = 0; i < num_rounds; ++i) { + const auto& table = crypto::pedersen::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::lookup::PEDERSEN_TABLE_SIZE); + } + + size_t hi_shift = 126; + const fr hi_cumulative = composer.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(composer.get_variable(lookup_witnesses_lo[ColumnIdx::C1][i]) + (hi_cumulative * hi_mult), + expected_scalars[i]); + EXPECT_EQ(composer.get_variable(lookup_witnesses_lo[ColumnIdx::C2][i]), expected_x[i]); + EXPECT_EQ(composer.get_variable(lookup_witnesses_lo[ColumnIdx::C3][i]), expected_y[i]); + hi_shift -= crypto::pedersen::lookup::BITS_PER_TABLE; + } + + for (size_t i = 0; i < num_lookups_hi; ++i) { + EXPECT_EQ(composer.get_variable(lookup_witnesses_hi[ColumnIdx::C1][i]), expected_scalars[i + num_lookups_lo]); + EXPECT_EQ(composer.get_variable(lookup_witnesses_hi[ColumnIdx::C2][i]), expected_x[i + num_lookups_lo]); + EXPECT_EQ(composer.get_variable(lookup_witnesses_hi[ColumnIdx::C3][i]), expected_y[i + num_lookups_lo]); + } + + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + auto proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); + + EXPECT_EQ(result, true); +} + +TEST(ultra_plonk_composer_splitting_tmp, test_no_lookup_proof) +{ + auto composer = UltraPlonkComposer(); + // UltraComposer composer = UltraComposer(); + + 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 = composer.add_variable(fr(left)); + uint32_t right_idx = composer.add_variable(fr(right)); + uint32_t result_idx = composer.add_variable(fr(left ^ right)); + + uint32_t add_idx = composer.add_variable(fr(left) + fr(right) + composer.get_variable(result_idx)); + composer.create_big_add_gate( + { left_idx, right_idx, result_idx, add_idx, fr(1), fr(1), fr(1), fr(-1), fr(0) }); + } + } + + auto prover = composer.create_prover(); + + auto verifier = composer.create_verifier(); + + auto proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); // instance, prover.reference_string.SRS_T2); + EXPECT_EQ(result, true); +} + +TEST(ultra_plonk_composer_splitting_tmp, test_elliptic_gate) +{ + typedef grumpkin::g1::affine_element affine_element; + typedef grumpkin::g1::element element; + auto composer = UltraPlonkComposer(); + + affine_element p1 = crypto::pedersen::get_generator_data({ 0, 0 }).generator; + + affine_element p2 = crypto::pedersen::get_generator_data({ 0, 1 }).generator; + affine_element p3(element(p1) + element(p2)); + + uint32_t x1 = composer.add_variable(p1.x); + uint32_t y1 = composer.add_variable(p1.y); + uint32_t x2 = composer.add_variable(p2.x); + uint32_t y2 = composer.add_variable(p2.y); + uint32_t x3 = composer.add_variable(p3.x); + uint32_t y3 = composer.add_variable(p3.y); + + ecc_add_gate gate{ x1, y1, x2, y2, x3, y3, 1, 1 }; + composer.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 = composer.add_variable(p3.x); + y3 = composer.add_variable(p3.y); + gate = ecc_add_gate{ x1, y1, x2, y2, x3, y3, beta, 1 }; + composer.create_ecc_add_gate(gate); + + p2_endo.x *= beta; + p3 = affine_element(element(p1) - element(p2_endo)); + x3 = composer.add_variable(p3.x); + y3 = composer.add_variable(p3.y); + gate = ecc_add_gate{ x1, y1, x2, y2, x3, y3, beta.sqr(), -1 }; + composer.create_ecc_add_gate(gate); + + auto prover = composer.create_prover(); + + auto verifier = composer.create_verifier(); + + auto proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); // instance, prover.reference_string.SRS_T2); + EXPECT_EQ(result, true); +} + +TEST(ultra_plonk_composer_splitting_tmp, non_trivial_tag_permutation) +{ + auto composer = UltraPlonkComposer(); + fr a = fr::random_element(); + fr b = -a; + + auto a_idx = composer.add_variable(a); + auto b_idx = composer.add_variable(b); + auto c_idx = composer.add_variable(b); + auto d_idx = composer.add_variable(a); + + composer.create_add_gate({ a_idx, b_idx, composer.get_zero_idx(), fr::one(), fr::one(), fr::zero(), fr::zero() }); + composer.create_add_gate({ c_idx, d_idx, composer.get_zero_idx(), fr::one(), fr::one(), fr::zero(), fr::zero() }); + + composer.create_tag(1, 2); + composer.create_tag(2, 1); + + composer.assign_tag(a_idx, 1); + composer.assign_tag(b_idx, 1); + composer.assign_tag(c_idx, 2); + composer.assign_tag(d_idx, 2); + + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + + proof proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); // instance, prover.reference_string.SRS_T2); + EXPECT_EQ(result, true); +} + +TEST(ultra_plonk_composer_splitting_tmp, non_trivial_tag_permutation_and_cycles) +{ + auto composer = UltraPlonkComposer(); + fr a = fr::random_element(); + fr c = -a; + + auto a_idx = composer.add_variable(a); + auto b_idx = composer.add_variable(a); + composer.assert_equal(a_idx, b_idx); + auto c_idx = composer.add_variable(c); + auto d_idx = composer.add_variable(c); + composer.assert_equal(c_idx, d_idx); + auto e_idx = composer.add_variable(a); + auto f_idx = composer.add_variable(a); + composer.assert_equal(e_idx, f_idx); + auto g_idx = composer.add_variable(c); + auto h_idx = composer.add_variable(c); + composer.assert_equal(g_idx, h_idx); + + composer.create_tag(1, 2); + composer.create_tag(2, 1); + + composer.assign_tag(a_idx, 1); + composer.assign_tag(c_idx, 1); + composer.assign_tag(e_idx, 2); + composer.assign_tag(g_idx, 2); + + composer.create_add_gate( + { b_idx, a_idx, composer.get_zero_idx(), fr::one(), fr::neg_one(), fr::zero(), fr::zero() }); + composer.create_add_gate({ c_idx, g_idx, composer.get_zero_idx(), fr::one(), -fr::one(), fr::zero(), fr::zero() }); + composer.create_add_gate({ e_idx, f_idx, composer.get_zero_idx(), fr::one(), -fr::one(), fr::zero(), fr::zero() }); + + auto prover = composer.create_prover(); + + auto verifier = composer.create_verifier(); + + proof proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); // instance, prover.reference_string.SRS_T2); + EXPECT_EQ(result, true); +} + +TEST(ultra_plonk_composer_splitting_tmp, bad_tag_permutation) +{ + auto composer = UltraPlonkComposer(); + fr a = fr::random_element(); + fr b = -a; + + auto a_idx = composer.add_variable(a); + auto b_idx = composer.add_variable(b); + auto c_idx = composer.add_variable(b); + auto d_idx = composer.add_variable(a + 1); + + composer.create_add_gate({ a_idx, b_idx, composer.get_zero_idx(), 1, 1, 0, 0 }); + composer.create_add_gate({ c_idx, d_idx, composer.get_zero_idx(), 1, 1, 0, -1 }); + + composer.create_tag(1, 2); + composer.create_tag(2, 1); + + composer.assign_tag(a_idx, 1); + composer.assign_tag(b_idx, 1); + composer.assign_tag(c_idx, 2); + composer.assign_tag(d_idx, 2); + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + proof proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); // instance, prover.reference_string.SRS_T2); + EXPECT_EQ(result, false); +} + +// same as above but with turbocomposer to check reason of failue is really tag mismatch +TEST(ultra_plonk_composer_splitting_tmp, bad_tag_turbo_permutation) +{ + auto composer = UltraPlonkComposer(); + fr a = fr::random_element(); + fr b = -a; + + auto a_idx = composer.add_variable(a); + auto b_idx = composer.add_variable(b); + auto c_idx = composer.add_variable(b); + auto d_idx = composer.add_variable(a + 1); + + composer.create_add_gate({ a_idx, b_idx, composer.get_zero_idx(), 1, 1, 0, 0 }); + composer.create_add_gate({ c_idx, d_idx, composer.get_zero_idx(), 1, 1, 0, -1 }); + + auto prover = composer.create_prover(); + + auto verifier = composer.create_verifier(); + + proof proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); // instance, prover.reference_string.SRS_T2); + EXPECT_EQ(result, true); +} + +TEST(ultra_plonk_composer_splitting_tmp, sort_widget) +{ + auto composer = UltraPlonkComposer(); + fr a = fr::one(); + fr b = fr(2); + fr c = fr(3); + fr d = fr(4); + + auto a_idx = composer.add_variable(a); + auto b_idx = composer.add_variable(b); + auto c_idx = composer.add_variable(c); + auto d_idx = composer.add_variable(d); + composer.create_sort_constraint({ a_idx, b_idx, c_idx, d_idx }); + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + + proof proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); // instance, prover.reference_string.SRS_T2); + EXPECT_EQ(result, true); +} + +TEST(ultra_plonk_composer_splitting_tmp, 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); + + { + auto composer = UltraPlonkComposer(); + auto a_idx = composer.add_variable(a); + auto b_idx = composer.add_variable(b); + auto c_idx = composer.add_variable(c); + auto d_idx = composer.add_variable(d); + auto e_idx = composer.add_variable(e); + auto f_idx = composer.add_variable(f); + auto g_idx = composer.add_variable(g); + auto h_idx = composer.add_variable(h); + composer.create_sort_constraint_with_edges({ a_idx, b_idx, c_idx, d_idx, e_idx, f_idx, g_idx, h_idx }, a, h); + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + + proof proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); // instance, prover.reference_string.SRS_T2); + EXPECT_EQ(result, true); + } + + { + auto composer = UltraPlonkComposer(); + auto a_idx = composer.add_variable(a); + auto b_idx = composer.add_variable(b); + auto c_idx = composer.add_variable(c); + auto d_idx = composer.add_variable(d); + auto e_idx = composer.add_variable(e); + auto f_idx = composer.add_variable(f); + auto g_idx = composer.add_variable(g); + auto h_idx = composer.add_variable(h); + composer.create_sort_constraint_with_edges({ a_idx, b_idx, c_idx, d_idx, e_idx, f_idx, g_idx, h_idx }, a, g); + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + + proof proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); // instance, prover.reference_string.SRS_T2); + EXPECT_EQ(result, false); + } + { + auto composer = UltraPlonkComposer(); + auto a_idx = composer.add_variable(a); + auto b_idx = composer.add_variable(b); + auto c_idx = composer.add_variable(c); + auto d_idx = composer.add_variable(d); + auto e_idx = composer.add_variable(e); + auto f_idx = composer.add_variable(f); + auto g_idx = composer.add_variable(g); + auto h_idx = composer.add_variable(h); + composer.create_sort_constraint_with_edges({ a_idx, b_idx, c_idx, d_idx, e_idx, f_idx, g_idx, h_idx }, b, h); + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + + proof proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); // instance, prover.reference_string.SRS_T2); + EXPECT_EQ(result, false); + } + { + auto composer = UltraPlonkComposer(); + auto a_idx = composer.add_variable(a); + auto c_idx = composer.add_variable(c); + auto d_idx = composer.add_variable(d); + auto e_idx = composer.add_variable(e); + auto f_idx = composer.add_variable(f); + auto g_idx = composer.add_variable(g); + auto h_idx = composer.add_variable(h); + auto b2_idx = composer.add_variable(fr(15)); + composer.create_sort_constraint_with_edges({ a_idx, b2_idx, c_idx, d_idx, e_idx, f_idx, g_idx, h_idx }, b, h); + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + + proof proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); // instance, prover.reference_string.SRS_T2); + EXPECT_EQ(result, false); + } + { + auto composer = UltraPlonkComposer(); + auto idx = add_variables(composer, { 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 }); + composer.create_sort_constraint_with_edges(idx, 1, 45); + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + + proof proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); // instance, prover.reference_string.SRS_T2); + EXPECT_EQ(result, true); + } + { + auto composer = UltraPlonkComposer(); + auto idx = add_variables(composer, { 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 }); + + composer.create_sort_constraint_with_edges(idx, 1, 29); + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + proof proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); // instance, prover.reference_string.SRS_T2); + EXPECT_EQ(result, false); + } +} + +TEST(ultra_plonk_composer_splitting_tmp, range_constraint) +{ + { + auto composer = UltraPlonkComposer(); + auto indices = add_variables(composer, { 1, 2, 3, 4, 5, 6, 7, 8 }); + for (size_t i = 0; i < indices.size(); i++) { + composer.create_new_range_constraint(indices[i], 8); + } + + composer.create_sort_constraint(indices); + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + + proof proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); + } + { + auto composer = UltraPlonkComposer(); + auto indices = add_variables(composer, { 3 }); + for (size_t i = 0; i < indices.size(); i++) { + composer.create_new_range_constraint(indices[i], 3); + } + + composer.create_dummy_constraints(indices); + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + + proof proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); + } + { + auto composer = UltraPlonkComposer(); + auto indices = add_variables(composer, { 1, 2, 3, 4, 5, 6, 8, 25 }); + for (size_t i = 0; i < indices.size(); i++) { + composer.create_new_range_constraint(indices[i], 8); + } + composer.create_sort_constraint(indices); + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + + proof proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, false); + } + { + auto composer = UltraPlonkComposer(); + auto indices = + add_variables(composer, { 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++) { + composer.create_new_range_constraint(indices[i], 128); + } + composer.create_dummy_constraints(indices); + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + + proof proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); + } + { + auto composer = UltraPlonkComposer(); + auto indices = + add_variables(composer, { 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++) { + composer.create_new_range_constraint(indices[i], 79); + } + composer.create_dummy_constraints(indices); + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + + proof proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, false); + } + { + auto composer = UltraPlonkComposer(); + auto indices = + add_variables(composer, { 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++) { + composer.create_new_range_constraint(indices[i], 79); + } + composer.create_dummy_constraints(indices); + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + + proof proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, false); + } +} + +TEST(ultra_plonk_composer_splitting_tmp, range_with_gates) +{ + + auto composer = UltraPlonkComposer(); + auto idx = add_variables(composer, { 1, 2, 3, 4, 5, 6, 7, 8 }); + for (size_t i = 0; i < idx.size(); i++) { + composer.create_new_range_constraint(idx[i], 8); + } + + composer.create_add_gate({ idx[0], idx[1], composer.get_zero_idx(), fr::one(), fr::one(), fr::zero(), -3 }); + composer.create_add_gate({ idx[2], idx[3], composer.get_zero_idx(), fr::one(), fr::one(), fr::zero(), -7 }); + composer.create_add_gate({ idx[4], idx[5], composer.get_zero_idx(), fr::one(), fr::one(), fr::zero(), -11 }); + composer.create_add_gate({ idx[6], idx[7], composer.get_zero_idx(), fr::one(), fr::one(), fr::zero(), -15 }); + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + + proof proof = prover.construct_proof(); + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); +} + +TEST(ultra_plonk_composer_splitting_tmp, range_with_gates_where_range_is_not_a_power_of_two) +{ + auto composer = UltraPlonkComposer(); + auto idx = add_variables(composer, { 1, 2, 3, 4, 5, 6, 7, 8 }); + for (size_t i = 0; i < idx.size(); i++) { + composer.create_new_range_constraint(idx[i], 12); + } + + composer.create_add_gate({ idx[0], idx[1], composer.get_zero_idx(), fr::one(), fr::one(), fr::zero(), -3 }); + composer.create_add_gate({ idx[2], idx[3], composer.get_zero_idx(), fr::one(), fr::one(), fr::zero(), -7 }); + composer.create_add_gate({ idx[4], idx[5], composer.get_zero_idx(), fr::one(), fr::one(), fr::zero(), -11 }); + composer.create_add_gate({ idx[6], idx[7], composer.get_zero_idx(), fr::one(), fr::one(), fr::zero(), -15 }); + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + + proof proof = prover.construct_proof(); + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); +} + +TEST(ultra_plonk_composer_splitting_tmp, sort_widget_complex) +{ + { + auto composer = UltraPlonkComposer(); + 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(composer.add_variable(a[i])); + composer.create_sort_constraint(ind); + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + + proof proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); // instance, prover.reference_string.SRS_T2); + EXPECT_EQ(result, true); + } + { + auto composer = UltraPlonkComposer(); + 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(composer.add_variable(a[i])); + composer.create_sort_constraint(ind); + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + + proof proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); // instance, prover.reference_string.SRS_T2); + EXPECT_EQ(result, false); + } +} + +TEST(ultra_plonk_composer_splitting_tmp, sort_widget_neg) +{ + auto composer = UltraPlonkComposer(); + fr a = fr::one(); + fr b = fr(2); + fr c = fr(3); + fr d = fr(8); + + auto a_idx = composer.add_variable(a); + auto b_idx = composer.add_variable(b); + auto c_idx = composer.add_variable(c); + auto d_idx = composer.add_variable(d); + composer.create_sort_constraint({ a_idx, b_idx, c_idx, d_idx }); + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + + proof proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, false); +} + +TEST(ultra_plonk_composer_splitting_tmp, composed_range_constraint) +{ + auto composer = UltraPlonkComposer(); + auto c = fr::random_element(); + auto d = uint256_t(c).slice(0, 133); + auto e = fr(d); + auto a_idx = composer.add_variable(fr(e)); + composer.create_add_gate({ a_idx, composer.get_zero_idx(), composer.get_zero_idx(), 1, 0, 0, -fr(e) }); + composer.decompose_into_default_range(a_idx, 134); + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + + proof proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); +} + +TEST(ultra_plonk_composer_splitting_tmp, non_native_field_multiplication) +{ + auto composer = UltraPlonkComposer(); + + 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] = composer.add_variable(limbs[0]); + limb_indices[1] = composer.add_variable(limbs[1]); + limb_indices[2] = composer.add_variable(limbs[2]); + limb_indices[3] = composer.add_variable(limbs[3]); + limb_indices[4] = composer.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))); + + bonk::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.evaluate_non_native_field_multiplication(inputs); + composer.range_constrain_two_limbs(lo_1_idx, hi_1_idx, 70, 70); + + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + + proof proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); +} + +TEST(ultra_plonk_composer_splitting_tmp, rom) +{ + auto composer = UltraPlonkComposer(); + + uint32_t rom_values[8]{ + composer.add_variable(fr::random_element()), composer.add_variable(fr::random_element()), + composer.add_variable(fr::random_element()), composer.add_variable(fr::random_element()), + composer.add_variable(fr::random_element()), composer.add_variable(fr::random_element()), + composer.add_variable(fr::random_element()), composer.add_variable(fr::random_element()), + }; + + size_t rom_id = composer.create_ROM_array(8); + + for (size_t i = 0; i < 8; ++i) { + composer.set_ROM_element(rom_id, i, rom_values[i]); + } + + uint32_t a_idx = composer.read_ROM_array(rom_id, composer.add_variable(5)); + EXPECT_EQ(a_idx != rom_values[5], true); + uint32_t b_idx = composer.read_ROM_array(rom_id, composer.add_variable(4)); + uint32_t c_idx = composer.read_ROM_array(rom_id, composer.add_variable(1)); + + const auto d_value = composer.get_variable(a_idx) + composer.get_variable(b_idx) + composer.get_variable(c_idx); + uint32_t d_idx = composer.add_variable(d_value); + + composer.create_big_add_gate({ + a_idx, + b_idx, + c_idx, + d_idx, + 1, + 1, + 1, + -1, + 0, + }); + + auto prover = composer.create_prover(); + info("composer.num_gates after constructing prover: ", composer.num_gates); + + auto verifier = composer.create_verifier(); + + proof proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); +} + +TEST(ultra_plonk_composer_splitting_tmp, ram) +{ + auto composer = UltraPlonkComposer(); + + uint32_t ram_values[8]{ + composer.add_variable(fr::random_element()), composer.add_variable(fr::random_element()), + composer.add_variable(fr::random_element()), composer.add_variable(fr::random_element()), + composer.add_variable(fr::random_element()), composer.add_variable(fr::random_element()), + composer.add_variable(fr::random_element()), composer.add_variable(fr::random_element()), + }; + + size_t ram_id = composer.create_RAM_array(8); + + for (size_t i = 0; i < 8; ++i) { + composer.init_RAM_element(ram_id, i, ram_values[i]); + } + + uint32_t a_idx = composer.read_RAM_array(ram_id, composer.add_variable(5)); + EXPECT_EQ(a_idx != ram_values[5], true); + + uint32_t b_idx = composer.read_RAM_array(ram_id, composer.add_variable(4)); + uint32_t c_idx = composer.read_RAM_array(ram_id, composer.add_variable(1)); + + composer.write_RAM_array(ram_id, composer.add_variable(4), composer.add_variable(500)); + uint32_t d_idx = composer.read_RAM_array(ram_id, composer.add_variable(4)); + + EXPECT_EQ(composer.get_variable(d_idx), 500); + + // ensure these vars get used in another arithmetic gate + const auto e_value = composer.get_variable(a_idx) + composer.get_variable(b_idx) + composer.get_variable(c_idx) + + composer.get_variable(d_idx); + uint32_t e_idx = composer.add_variable(e_value); + + composer.create_big_add_gate( + { + a_idx, + b_idx, + c_idx, + d_idx, + -1, + -1, + -1, + -1, + 0, + }, + true); + composer.create_big_add_gate( + { + composer.get_zero_idx(), + composer.get_zero_idx(), + composer.get_zero_idx(), + e_idx, + 0, + 0, + 0, + 0, + 0, + }, + false); + + auto prover = composer.create_prover(); + std::cout << "prover num_gates = " << composer.num_gates << std::endl; + + auto verifier = composer.create_verifier(); + + proof proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); +} + +} // namespace plonk diff --git a/cpp/src/barretenberg/polynomials/polynomial.hpp b/cpp/src/barretenberg/polynomials/polynomial.hpp index 43853fd88a..fed986d00c 100644 --- a/cpp/src/barretenberg/polynomials/polynomial.hpp +++ b/cpp/src/barretenberg/polynomials/polynomial.hpp @@ -54,25 +54,17 @@ template class Polynomial { if (is_empty() || rhs.is_empty()) { return is_empty() && rhs.is_empty(); } - // Otherwise, check that the coefficients match on the minimum of the two sizes and that the higher coefficients - // of the larger poly (if one exists) are identically zero. - else { - size_t min_size = std::min(size(), rhs.size()); - for (size_t i = 0; i < min_size; i++) { - if (coefficients_[i] != rhs.coefficients_[i]) - return false; - } - for (size_t i = min_size; i < size(); i++) { - if (coefficients_[i] != 0) - return false; - } - for (size_t i = min_size; i < rhs.size(); i++) { - if (rhs.coefficients_[i] != 0) - return false; + // Size must agree + if (size() != rhs.size()) { + return false; + } + // Each coefficient must agree + for (size_t i = 0; i < size(); i++) { + if (coefficients_[i] != rhs.coefficients_[i]) { + return false; } - - return true; } + return true; } // IMPROVEMENT: deprecate in favor of 'data()' and ensure const correctness diff --git a/cpp/src/barretenberg/proof_system/arithmetization/arithmetization.hpp b/cpp/src/barretenberg/proof_system/arithmetization/arithmetization.hpp index 3aa2a13dba..a2f2207084 100644 --- a/cpp/src/barretenberg/proof_system/arithmetization/arithmetization.hpp +++ b/cpp/src/barretenberg/proof_system/arithmetization/arithmetization.hpp @@ -36,5 +36,6 @@ template struct Arithmetization { // through clearly named static class members. using Standard = Arithmetization; using Turbo = Arithmetization; +using Ultra = Arithmetization; } // namespace arithmetization \ No newline at end of file diff --git a/cpp/src/barretenberg/proof_system/circuit_constructors/ultra_circuit_constructor.cpp b/cpp/src/barretenberg/proof_system/circuit_constructors/ultra_circuit_constructor.cpp new file mode 100644 index 0000000000..c26a3380f5 --- /dev/null +++ b/cpp/src/barretenberg/proof_system/circuit_constructors/ultra_circuit_constructor.cpp @@ -0,0 +1,2324 @@ +#include "ultra_circuit_constructor.hpp" +#include +#include + +using namespace barretenberg; + +namespace bonk { + +void UltraCircuitConstructor::finalize_circuit() +{ + /** + * First of all, add the gates related to ROM arrays and range lists. + * Note that the total number of rows in an UltraPlonk program can be divided as following: + * 1. arithmetic gates: n_computation (includes all computation gates) + * 2. rom/memory gates: n_rom + * 3. range list gates: n_range + * 4. public inputs: n_pub + * + * Now we have two variables referred to as `n` in the code: + * 1. ComposerBase::n => refers to the size of the witness of a given program, + * 2. proving_key::n => the next power of two ≥ total witness size. + * + * In this case, we have composer.num_gates = n_computation before we execute the following two functions. + * After these functions are executed, the composer's `n` is incremented to include the ROM + * and range list gates. Therefore we have: + * composer.num_gates = n_computation + n_rom + n_range. + * + * Its necessary to include the (n_rom + n_range) gates at this point because if we already have a + * proving key, and we just return it without including these ROM and range list gates, the overall + * circuit size would not be correct (resulting in the code crashing while performing FFT operations). + * + * Therefore, we introduce a boolean flag `circuit_finalised` here. Once we add the rom and range gates, + * our circuit is finalised, and we must not to execute these functions again. + */ + if (!circuit_finalised) { + process_ROM_arrays(public_inputs.size()); + process_RAM_arrays(public_inputs.size()); + process_range_lists(); + circuit_finalised = true; + } +} + +/** + * @brief Create an addition gate, where in.a * in.a_scaling + in.b * in.b_scaling + in.c * in.c_scaling + + * in.const_scaling = 0 + * + * @details Arithmetic selector is set to 1, all other gate selectors are 0. Mutliplication selector is set to 0 + * + * @param in A structure with variable indexes and selector values for the gate. + */ +void UltraCircuitConstructor::create_add_gate(const add_triple& in) +{ + assert_valid_variables({ in.a, in.b, in.c }); + + w_l.emplace_back(in.a); + w_r.emplace_back(in.b); + w_o.emplace_back(in.c); + w_4.emplace_back(zero_idx); + q_m.emplace_back(0); + q_1.emplace_back(in.a_scaling); + q_2.emplace_back(in.b_scaling); + q_3.emplace_back(in.c_scaling); + q_c.emplace_back(in.const_scaling); + q_arith.emplace_back(1); + q_4.emplace_back(0); + q_sort.emplace_back(0); + q_lookup_type.emplace_back(0); + q_elliptic.emplace_back(0); + q_aux.emplace_back(0); + ++num_gates; +} + +/** + * @brief Create a big addition gate, where in.a * in.a_scaling + in.b * in.b_scaling + in.c * in.c_scaling + in.d * + * in.d_scaling + in.const_scaling = 0. If include_next_gate_w_4 is enabled, then thes sum also adds the value of the + * 4-th witness at the next index. + * + * @param in Structure with variable indexes and wire selector values + * @param include_next_gate_w_4 Switches on/off the addition of w_4 at the next index + */ +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); + w_4.emplace_back(in.d); + q_m.emplace_back(0); + q_1.emplace_back(in.a_scaling); + q_2.emplace_back(in.b_scaling); + q_3.emplace_back(in.c_scaling); + q_c.emplace_back(in.const_scaling); + q_arith.emplace_back(include_next_gate_w_4 ? 2 : 1); + q_4.emplace_back(in.d_scaling); + q_sort.emplace_back(0); + q_lookup_type.emplace_back(0); + q_elliptic.emplace_back(0); + q_aux.emplace_back(0); + ++num_gates; +} + +/** + * @brief A legacy method that was used to extract a bit from c-4d by using gate selectors in the Turboplonk, but is + * simulated here for ultraplonk + * + * @param in Structure with variables and witness selector values + */ +void UltraCircuitConstructor::create_big_add_gate_with_bit_extraction(const add_quad& in) +{ + // This method is an artifact of a turbo plonk feature that implicitly extracts + // a high or low bit from a base-4 quad and adds it into the arithmetic gate relationship. + // This has been removed in the plookup composer due to it's infrequent use not being worth the extra + // cost incurred by the prover for the extra field muls required. + + // We have wires a, b, c, d, where + // a + b + c + d + 6 * (extracted bit) = 0 + // (extracted bit) is the high bit pulled from c - 4d + + assert_valid_variables({ in.a, in.b, in.c, in.d }); + + const uint256_t quad = get_variable(in.c) - get_variable(in.d) * 4; + const auto lo_bit = quad & uint256_t(1); + const auto hi_bit = (quad & uint256_t(2)) >> 1; + const auto lo_idx = add_variable(lo_bit); + const auto hi_idx = add_variable(hi_bit); + // lo + hi * 2 - c + 4 * d = 0 + create_big_add_gate({ + lo_idx, + hi_idx, + in.c, + in.d, + 1, + 2, + -1, + 4, + 0, + }); + + // create temporary variable t = in.a * in.a_scaling + 6 * hi_bit + const auto t = get_variable(in.a) * in.a_scaling + fr(hi_bit) * 6; + const auto t_idx = add_variable(t); + create_big_add_gate({ + in.a, + hi_idx, + t_idx, + zero_idx, + in.a_scaling, + 6, + -1, + 0, + 0, + }); + // (t = a + 6 * hi_bit) + b + c + d = 0 + create_big_add_gate({ + t_idx, + in.b, + in.c, + in.d, + 1, + in.b_scaling, + in.c_scaling, + in.d_scaling, + in.const_scaling, + }); +} +/** + * @brief Create a basic multiplication gate q_m * a * b + q_1 * a + q_2 * b + q_3 * c + q_4 * d + q_c = 0 (q_arith = 1) + * + * @param in Structure containing variables and witness selectors + */ +void UltraCircuitConstructor::create_big_mul_gate(const mul_quad& in) +{ + 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); + w_4.emplace_back(in.d); + q_m.emplace_back(in.mul_scaling); + q_1.emplace_back(in.a_scaling); + q_2.emplace_back(in.b_scaling); + q_3.emplace_back(in.c_scaling); + q_c.emplace_back(in.const_scaling); + q_arith.emplace_back(1); + q_4.emplace_back(in.d_scaling); + q_sort.emplace_back(0); + q_lookup_type.emplace_back(0); + q_elliptic.emplace_back(0); + q_aux.emplace_back(0); + ++num_gates; +} + +// Creates a width-4 addition gate, where the fourth witness must be a boolean. +// Can be used to normalize a 32-bit addition +void UltraCircuitConstructor::create_balanced_add_gate(const add_quad& in) +{ + 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); + w_4.emplace_back(in.d); + q_m.emplace_back(0); + q_1.emplace_back(in.a_scaling); + q_2.emplace_back(in.b_scaling); + q_3.emplace_back(in.c_scaling); + q_c.emplace_back(in.const_scaling); + q_arith.emplace_back(1); + q_4.emplace_back(in.d_scaling); + q_sort.emplace_back(0); + q_lookup_type.emplace_back(0); + q_elliptic.emplace_back(0); + q_aux.emplace_back(0); + ++num_gates; + // Why 3? TODO: return to this + // The purpose of this gate is to do enable lazy 32-bit addition. + // Consider a + b = c mod 2^32 + // We want the 4th wire to represent the quotient: + // w1 + w2 = w4 * 2^32 + w3 + // If we allow this overflow 'flag' to range from 0 to 3, instead of 0 to 1, + // we can get away with chaining a few addition operations together with basic add gates, + // before having to use this gate. + // (N.B. a larger value would be better, the value '3' is for TurboPlonk backwards compatibility. + // In TurboPlonk this method uses a custom gate, + // where we were limited to a 2-bit range check by the degree of the custom gate identity. + create_new_range_constraint(in.d, 3); +} +/** + * @brief Create a multiplication gate with q_m * a * b + q_3 * c + q_const = 0 + * + * @details q_arith == 1 + * + * @param in Structure containing variables and witness selectors + */ +void UltraCircuitConstructor::create_mul_gate(const mul_triple& in) +{ + assert_valid_variables({ in.a, in.b, in.c }); + + w_l.emplace_back(in.a); + w_r.emplace_back(in.b); + w_o.emplace_back(in.c); + w_4.emplace_back(zero_idx); + q_m.emplace_back(in.mul_scaling); + q_1.emplace_back(0); + q_2.emplace_back(0); + q_3.emplace_back(in.c_scaling); + q_c.emplace_back(in.const_scaling); + q_arith.emplace_back(1); + q_4.emplace_back(0); + q_sort.emplace_back(0); + q_lookup_type.emplace_back(0); + q_elliptic.emplace_back(0); + q_aux.emplace_back(0); + ++num_gates; +} +/** + * @brief Generate an arithmetic gate equivalent to x^2 - x = 0, which forces x to be 0 or 1 + * + * @param variable_index Variable which needs to be constrained + */ +void UltraCircuitConstructor::create_bool_gate(const uint32_t variable_index) +{ + assert_valid_variables({ variable_index }); + + w_l.emplace_back(variable_index); + w_r.emplace_back(variable_index); + w_o.emplace_back(zero_idx); + w_4.emplace_back(zero_idx); + q_m.emplace_back(1); + q_1.emplace_back(-1); + q_2.emplace_back(0); + q_3.emplace_back(0); + q_c.emplace_back(0); + q_sort.emplace_back(0); + + q_arith.emplace_back(1); + q_4.emplace_back(0); + q_lookup_type.emplace_back(0); + q_elliptic.emplace_back(0); + q_aux.emplace_back(0); + ++num_gates; +} + +/** + * @brief A plonk gate with disabled (set to zero) fourth wire. q_m * a * b + q_1 * a + q_2 * b + q_3 * c + q_const = 0 + * + * @param in Structure containing variables and witness selectors + */ +void UltraCircuitConstructor::create_poly_gate(const poly_triple& in) +{ + assert_valid_variables({ in.a, in.b, in.c }); + + w_l.emplace_back(in.a); + w_r.emplace_back(in.b); + w_o.emplace_back(in.c); + w_4.emplace_back(zero_idx); + q_m.emplace_back(in.q_m); + q_1.emplace_back(in.q_l); + q_2.emplace_back(in.q_r); + q_3.emplace_back(in.q_o); + q_c.emplace_back(in.q_c); + q_sort.emplace_back(0); + + q_arith.emplace_back(1); + q_4.emplace_back(0); + q_lookup_type.emplace_back(0); + q_elliptic.emplace_back(0); + q_aux.emplace_back(0); + ++num_gates; +} + +/** + * @brief Create an elliptic curve addition gate + * + * @details x and y are defined over scalar field. Addition can handle applying the curve endomorphism to one of the + * points being summed at the time of addition. + * + * @param in Elliptic curve point addition gate parameters, including the the affine coordinates of the two points being + * added, the resulting point coordinates and the selector values that describe whether the endomorphism is used on the + * second point and whether it is negated. + */ +void UltraCircuitConstructor::create_ecc_add_gate(const ecc_add_gate& in) +{ + /** + * | 1 | 2 | 3 | 4 | + * | a1 | a2 | x1 | y1 | + * | x2 | y2 | x3 | y3 | + * | -- | -- | x4 | y4 | + * + **/ + + assert_valid_variables({ in.x1, in.x2, in.x3, in.y1, in.y2, in.y3 }); + + bool can_fuse_into_previous_gate = true; + can_fuse_into_previous_gate = can_fuse_into_previous_gate && (w_r[num_gates - 1] == in.x1); + can_fuse_into_previous_gate = can_fuse_into_previous_gate && (w_o[num_gates - 1] == in.y1); + can_fuse_into_previous_gate = can_fuse_into_previous_gate && (q_3[num_gates - 1] == 0); + can_fuse_into_previous_gate = can_fuse_into_previous_gate && (q_4[num_gates - 1] == 0); + can_fuse_into_previous_gate = can_fuse_into_previous_gate && (q_1[num_gates - 1] == 0); + can_fuse_into_previous_gate = can_fuse_into_previous_gate && (q_arith[num_gates - 1] == 0); + + if (can_fuse_into_previous_gate) { + + q_3[num_gates - 1] = in.endomorphism_coefficient; + q_4[num_gates - 1] = in.endomorphism_coefficient.sqr(); + q_1[num_gates - 1] = in.sign_coefficient; + q_elliptic[num_gates - 1] = 1; + } else { + w_l.emplace_back(zero_idx); + w_r.emplace_back(in.x1); + w_o.emplace_back(in.y1); + w_4.emplace_back(zero_idx); + q_3.emplace_back(in.endomorphism_coefficient); + q_4.emplace_back(in.endomorphism_coefficient.sqr()); + q_1.emplace_back(in.sign_coefficient); + + q_arith.emplace_back(0); + q_2.emplace_back(0); + q_m.emplace_back(0); + q_c.emplace_back(0); + q_sort.emplace_back(0); + q_lookup_type.emplace_back(0); + q_elliptic.emplace_back(1); + q_aux.emplace_back(0); + ++num_gates; + } + + w_l.emplace_back(in.x2); + w_4.emplace_back(in.y2); + w_r.emplace_back(in.x3); + w_o.emplace_back(in.y3); + q_m.emplace_back(0); + q_1.emplace_back(0); + q_2.emplace_back(0); + q_3.emplace_back(0); + q_c.emplace_back(0); + q_arith.emplace_back(0); + q_4.emplace_back(0); + q_sort.emplace_back(0); + q_lookup_type.emplace_back(0); + q_elliptic.emplace_back(0); + q_aux.emplace_back(0); + ++num_gates; +} + +/** + * @brief Add a gate equating a particular witness to a constant, fixing it the value + * + * @param witness_index The index of the witness we are fixing + * @param witness_value The value we are fixing it to + */ +void UltraCircuitConstructor::fix_witness(const uint32_t witness_index, const barretenberg::fr& witness_value) +{ + assert_valid_variables({ witness_index }); + + w_l.emplace_back(witness_index); + w_r.emplace_back(zero_idx); + w_o.emplace_back(zero_idx); + w_4.emplace_back(zero_idx); + q_m.emplace_back(0); + q_1.emplace_back(1); + q_2.emplace_back(0); + q_3.emplace_back(0); + q_c.emplace_back(-witness_value); + q_arith.emplace_back(1); + q_4.emplace_back(0); + q_sort.emplace_back(0); + q_lookup_type.emplace_back(0); + q_elliptic.emplace_back(0); + q_aux.emplace_back(0); + ++num_gates; +} + +uint32_t UltraCircuitConstructor::put_constant_variable(const barretenberg::fr& variable) +{ + if (constant_variable_indices.contains(variable)) { + return constant_variable_indices.at(variable); + } else { + uint32_t variable_index = add_variable(variable); + fix_witness(variable_index, variable); + constant_variable_indices.insert({ variable, variable_index }); + return variable_index; + } +} + +plookup::BasicTable& UltraCircuitConstructor::get_table(const plookup::BasicTableId id) +{ + for (plookup::BasicTable& table : lookup_tables) { + if (table.id == id) { + return table; + } + } + // Table doesn't exist! So try to create it. + lookup_tables.emplace_back(plookup::create_basic_table(id, lookup_tables.size())); + return lookup_tables[lookup_tables.size() - 1]; +} + +/** + * @brief Perform a series of lookups, one for each 'row' in read_values. + */ +plookup::ReadData UltraCircuitConstructor::create_gates_from_plookup_accumulators( + const plookup::MultiTableId& id, + const plookup::ReadData& read_values, + const uint32_t key_a_index, + std::optional key_b_index) +{ + const auto& multi_table = plookup::create_table(id); + const size_t num_lookups = read_values[plookup::ColumnIdx::C1].size(); + plookup::ReadData read_data; + for (size_t i = 0; i < num_lookups; ++i) { + auto& table = get_table(multi_table.lookup_ids[i]); + + table.lookup_gates.emplace_back(read_values.key_entries[i]); + + const auto first_idx = (i == 0) ? key_a_index : add_variable(read_values[plookup::ColumnIdx::C1][i]); + const auto second_idx = (i == 0 && (key_b_index.has_value())) + ? key_b_index.value() + : add_variable(read_values[plookup::ColumnIdx::C2][i]); + const auto third_idx = add_variable(read_values[plookup::ColumnIdx::C3][i]); + + read_data[plookup::ColumnIdx::C1].push_back(first_idx); + read_data[plookup::ColumnIdx::C2].push_back(second_idx); + read_data[plookup::ColumnIdx::C3].push_back(third_idx); + assert_valid_variables({ first_idx, second_idx, third_idx }); + + q_lookup_type.emplace_back(fr(1)); + q_3.emplace_back(fr(table.table_index)); + w_l.emplace_back(first_idx); + w_r.emplace_back(second_idx); + w_o.emplace_back(third_idx); + w_4.emplace_back(zero_idx); + q_1.emplace_back(0); + q_2.emplace_back((i == (num_lookups - 1) ? 0 : -multi_table.column_1_step_sizes[i + 1])); + q_m.emplace_back((i == (num_lookups - 1) ? 0 : -multi_table.column_2_step_sizes[i + 1])); + q_c.emplace_back((i == (num_lookups - 1) ? 0 : -multi_table.column_3_step_sizes[i + 1])); + q_arith.emplace_back(0); + q_4.emplace_back(0); + q_sort.emplace_back(0); + q_elliptic.emplace_back(0); + q_aux.emplace_back(0); + ++num_gates; + } + return read_data; +} + +/** + * Generalized Permutation Methods + **/ + +RangeList UltraCircuitConstructor::create_range_list(const uint64_t target_range) +{ + RangeList result; + const auto range_tag = get_new_tag(); // current_tag + 1; + const auto tau_tag = get_new_tag(); // current_tag + 2; + create_tag(range_tag, tau_tag); + create_tag(tau_tag, range_tag); + result.target_range = target_range; + result.range_tag = range_tag; + result.tau_tag = tau_tag; + + uint64_t num_multiples_of_three = (target_range / DEFAULT_PLOOKUP_RANGE_STEP_SIZE); + + result.variable_indices.reserve((uint32_t)num_multiples_of_three); + for (uint64_t i = 0; i <= num_multiples_of_three; ++i) { + const uint32_t index = add_variable(i * DEFAULT_PLOOKUP_RANGE_STEP_SIZE); + result.variable_indices.emplace_back(index); + assign_tag(index, result.range_tag); + } + { + const uint32_t index = add_variable(target_range); + result.variable_indices.emplace_back(index); + assign_tag(index, result.range_tag); + } + // Need this because these variables will not appear in the witness otherwise + create_dummy_constraints(result.variable_indices); + + return result; +} + +// range constraint a value by decomposing it into limbs whose size should be the default range constraint size +std::vector UltraCircuitConstructor::decompose_into_default_range(const uint32_t variable_index, + const uint64_t num_bits, + const uint64_t target_range_bitnum, + std::string const& msg) +{ + assert_valid_variables({ variable_index }); + + ASSERT(num_bits > 0); + + uint256_t val = (uint256_t)(get_variable(variable_index)); + + // If the value is out of range, set the composer error to the given msg. + if (val.get_msb() >= num_bits && !failed()) { + failure(msg); + } + + const uint64_t sublimb_mask = (1ULL << target_range_bitnum) - 1; + + /** + * TODO: Support this commented-out code! + * At the moment, `decompose_into_default_range` generates a minimum of 1 arithmetic gate. + * This is not strictly required iff num_bits <= target_range_bitnum. + * However, this produces an edge-case where a variable is range-constrained but NOT present in an arithmetic gate. + * This in turn produces an unsatisfiable circuit (see `create_new_range_constraint`). We would need to check for + * and accomodate/reject this edge case to support not adding addition gates here if not reqiured + * if (num_bits <= target_range_bitnum) { + * const uint64_t expected_range = (1ULL << num_bits) - 1ULL; + * create_new_range_constraint(variable_index, expected_range); + * return { variable_index }; + * } + **/ + std::vector sublimbs; + std::vector sublimb_indices; + + const bool has_remainder_bits = (num_bits % target_range_bitnum != 0); + const uint64_t num_limbs = (num_bits / target_range_bitnum) + has_remainder_bits; + const uint64_t last_limb_size = num_bits - ((num_bits / target_range_bitnum) * target_range_bitnum); + const uint64_t last_limb_range = ((uint64_t)1 << last_limb_size) - 1; + + uint256_t accumulator = val; + for (size_t i = 0; i < num_limbs; ++i) { + sublimbs.push_back(accumulator.data[0] & sublimb_mask); + accumulator = accumulator >> target_range_bitnum; + } + for (size_t i = 0; i < sublimbs.size(); ++i) { + const auto limb_idx = add_variable(sublimbs[i]); + sublimb_indices.emplace_back(limb_idx); + if ((i == sublimbs.size() - 1) && has_remainder_bits) { + create_new_range_constraint(limb_idx, last_limb_range); + } else { + create_new_range_constraint(limb_idx, sublimb_mask); + } + } + + const uint64_t num_limb_triples = (num_limbs / 3) + ((num_limbs % 3) != 0); + const uint64_t leftovers = (num_limbs % 3) == 0 ? 3 : (num_limbs % 3); + + accumulator = val; + uint32_t accumulator_idx = variable_index; + + for (size_t i = 0; i < num_limb_triples; ++i) { + const bool real_limbs[3]{ + (i == (num_limb_triples - 1) && (leftovers < 1)) ? false : true, + (i == (num_limb_triples - 1) && (leftovers < 2)) ? false : true, + (i == (num_limb_triples - 1) && (leftovers < 3)) ? false : true, + }; + + const uint64_t round_sublimbs[3]{ + real_limbs[0] ? sublimbs[3 * i] : 0, + real_limbs[1] ? sublimbs[3 * i + 1] : 0, + real_limbs[2] ? sublimbs[3 * i + 2] : 0, + }; + const uint32_t new_limbs[3]{ + real_limbs[0] ? sublimb_indices[3 * i] : zero_idx, + real_limbs[1] ? sublimb_indices[3 * i + 1] : zero_idx, + real_limbs[2] ? sublimb_indices[3 * i + 2] : zero_idx, + }; + const uint64_t shifts[3]{ + target_range_bitnum * (3 * i), + target_range_bitnum * (3 * i + 1), + target_range_bitnum * (3 * i + 2), + }; + uint256_t new_accumulator = accumulator - (uint256_t(round_sublimbs[0]) << shifts[0]) - + (uint256_t(round_sublimbs[1]) << shifts[1]) - + (uint256_t(round_sublimbs[2]) << shifts[2]); + + create_big_add_gate( + { + new_limbs[0], + new_limbs[1], + new_limbs[2], + accumulator_idx, + uint256_t(1) << shifts[0], + uint256_t(1) << shifts[1], + uint256_t(1) << shifts[2], + -1, + 0, + }, + ((i == num_limb_triples - 1) ? false : true)); + accumulator_idx = add_variable(new_accumulator); + accumulator = new_accumulator; + } + return sublimb_indices; +} + +/** + * @brief Constrain a variable to a range + * + * @details Checks if the range [0, target_range] already exists. If it doesn't, then creates a new range. Then tags + * variable as belonging to this set. + * + * @param variable_index + * @param target_range + */ +void UltraCircuitConstructor::create_new_range_constraint(const uint32_t variable_index, + const uint64_t target_range, + std::string const msg) +{ + if (uint256_t(get_variable(variable_index)).data[0] > target_range) { + if (!failed()) { + failure(msg); + } + } + if (range_lists.count(target_range) == 0) { + range_lists.insert({ target_range, create_range_list(target_range) }); + } + + auto& list = range_lists[target_range]; + assign_tag(variable_index, list.range_tag); + list.variable_indices.emplace_back(variable_index); +} + +void UltraCircuitConstructor::process_range_list(const RangeList& list) +{ + assert_valid_variables(list.variable_indices); + + ASSERT(list.variable_indices.size() > 0); + // go over variables + // for each variable, create mirror variable with same value - with tau tag + // need to make sure that, in original list, increments of at most 3 + std::vector sorted_list; + sorted_list.reserve(list.variable_indices.size()); + for (const auto variable_index : list.variable_indices) { + const auto& field_element = get_variable(variable_index); + const uint64_t shrinked_value = field_element.from_montgomery_form().data[0]; + sorted_list.emplace_back(shrinked_value); + } + +#ifdef NO_TBB + std::sort(sorted_list.begin(), sorted_list.end()); +#else + std::sort(std::execution::par_unseq, sorted_list.begin(), sorted_list.end()); +#endif + std::vector indices; + + // list must be padded to a multipe of 4 and larger than 4 (gate_width) + constexpr size_t gate_width = plonk::ultra_settings::program_width; + size_t padding = (gate_width - (list.variable_indices.size() % gate_width)) % gate_width; + if (list.variable_indices.size() <= gate_width) + padding += gate_width; + for (size_t i = 0; i < padding; ++i) { + indices.emplace_back(zero_idx); + } + for (const auto sorted_value : sorted_list) { + const uint32_t index = add_variable(sorted_value); + assign_tag(index, list.tau_tag); + indices.emplace_back(index); + } + create_sort_constraint_with_edges(indices, 0, list.target_range); +} + +void UltraCircuitConstructor::process_range_lists() +{ + for (const auto& i : range_lists) + process_range_list(i.second); +} + +/* + Create range constraint: + * add variable index to a list of range constrained variables + * data structures: vector of lists, each list contains: + * - the range size + * - the list of variables in the range + * - a generalised permutation tag + * + * create range constraint parameters: variable index && range size + * + * std::map range_lists; +*/ +// Check for a sequence of variables that neighboring differences are at most 3 (used for batched range checkj) +void UltraCircuitConstructor::create_sort_constraint(const std::vector& variable_index) +{ + constexpr size_t gate_width = plonk::ultra_settings::program_width; + ASSERT(variable_index.size() % gate_width == 0); + assert_valid_variables(variable_index); + + for (size_t i = 0; i < variable_index.size(); i += gate_width) { + + w_l.emplace_back(variable_index[i]); + w_r.emplace_back(variable_index[i + 1]); + w_o.emplace_back(variable_index[i + 2]); + w_4.emplace_back(variable_index[i + 3]); + ++num_gates; + q_m.emplace_back(0); + q_1.emplace_back(0); + q_2.emplace_back(0); + q_3.emplace_back(0); + q_c.emplace_back(0); + q_arith.emplace_back(0); + q_4.emplace_back(0); + q_sort.emplace_back(1); + q_elliptic.emplace_back(0); + q_lookup_type.emplace_back(0); + q_aux.emplace_back(0); + } + // dummy gate needed because of sort widget's check of next row + w_l.emplace_back(variable_index[variable_index.size() - 1]); + w_r.emplace_back(zero_idx); + w_o.emplace_back(zero_idx); + w_4.emplace_back(zero_idx); + ++num_gates; + q_m.emplace_back(0); + q_1.emplace_back(0); + q_2.emplace_back(0); + q_3.emplace_back(0); + q_c.emplace_back(0); + q_arith.emplace_back(0); + q_4.emplace_back(0); + q_sort.emplace_back(0); + q_elliptic.emplace_back(0); + q_lookup_type.emplace_back(0); + q_aux.emplace_back(0); +} + +// useful to put variables in the witness that aren't already used - e.g. the dummy variables of the range constraint in +// multiples of three +void UltraCircuitConstructor::create_dummy_constraints(const std::vector& variable_index) +{ + std::vector padded_list = variable_index; + constexpr size_t gate_width = plonk::ultra_settings::program_width; + const uint64_t padding = (gate_width - (padded_list.size() % gate_width)) % gate_width; + for (uint64_t i = 0; i < padding; ++i) { + padded_list.emplace_back(zero_idx); + } + assert_valid_variables(variable_index); + assert_valid_variables(padded_list); + + for (size_t i = 0; i < padded_list.size(); i += gate_width) { + w_l.emplace_back(padded_list[i]); + w_r.emplace_back(padded_list[i + 1]); + w_o.emplace_back(padded_list[i + 2]); + w_4.emplace_back(padded_list[i + 3]); + ++num_gates; + q_m.emplace_back(0); + q_1.emplace_back(0); + q_2.emplace_back(0); + q_3.emplace_back(0); + q_c.emplace_back(0); + q_arith.emplace_back(0); + q_4.emplace_back(0); + q_sort.emplace_back(0); + q_elliptic.emplace_back(0); + q_lookup_type.emplace_back(0); + q_aux.emplace_back(0); + } +} + +// Check for a sequence of variables that neighboring differences are at most 3 (used for batched range checks) +void UltraCircuitConstructor::create_sort_constraint_with_edges(const std::vector& variable_index, + const fr& start, + const fr& end) +{ + // Convenient to assume size is at least 8 (gate_width = 4) for separate gates for start and end conditions + constexpr size_t gate_width = plonk::ultra_settings::program_width; + ASSERT(variable_index.size() % gate_width == 0 && variable_index.size() > gate_width); + assert_valid_variables(variable_index); + + // enforce range checks of first row and starting at start + w_l.emplace_back(variable_index[0]); + w_r.emplace_back(variable_index[1]); + w_o.emplace_back(variable_index[2]); + w_4.emplace_back(variable_index[3]); + ++num_gates; + q_m.emplace_back(0); + q_1.emplace_back(1); + q_2.emplace_back(0); + q_3.emplace_back(0); + q_c.emplace_back(-start); + q_arith.emplace_back(1); + q_4.emplace_back(0); + q_sort.emplace_back(1); + q_elliptic.emplace_back(0); + q_lookup_type.emplace_back(0); + q_aux.emplace_back(0); + // enforce range check for middle rows + for (size_t i = gate_width; i < variable_index.size() - gate_width; i += gate_width) { + + w_l.emplace_back(variable_index[i]); + w_r.emplace_back(variable_index[i + 1]); + w_o.emplace_back(variable_index[i + 2]); + w_4.emplace_back(variable_index[i + 3]); + ++num_gates; + q_m.emplace_back(0); + q_1.emplace_back(0); + q_2.emplace_back(0); + q_3.emplace_back(0); + q_c.emplace_back(0); + q_arith.emplace_back(0); + q_4.emplace_back(0); + q_sort.emplace_back(1); + q_elliptic.emplace_back(0); + q_lookup_type.emplace_back(0); + q_aux.emplace_back(0); + } + // enforce range checks of last row and ending at end + if (variable_index.size() > gate_width) { + w_l.emplace_back(variable_index[variable_index.size() - 4]); + w_r.emplace_back(variable_index[variable_index.size() - 3]); + w_o.emplace_back(variable_index[variable_index.size() - 2]); + w_4.emplace_back(variable_index[variable_index.size() - 1]); + ++num_gates; + q_m.emplace_back(0); + q_1.emplace_back(0); + q_2.emplace_back(0); + q_3.emplace_back(0); + q_c.emplace_back(0); + q_arith.emplace_back(0); + q_4.emplace_back(0); + q_sort.emplace_back(1); + q_elliptic.emplace_back(0); + q_lookup_type.emplace_back(0); + q_aux.emplace_back(0); + } + + // dummy gate needed because of sort widget's check of next row + // use this gate to check end condition + w_l.emplace_back(variable_index[variable_index.size() - 1]); + w_r.emplace_back(zero_idx); + w_o.emplace_back(zero_idx); + w_4.emplace_back(zero_idx); + ++num_gates; + q_m.emplace_back(0); + q_1.emplace_back(1); + q_2.emplace_back(0); + q_3.emplace_back(0); + q_c.emplace_back(-end); + q_arith.emplace_back(1); + q_4.emplace_back(0); + q_sort.emplace_back(0); + q_elliptic.emplace_back(0); + q_lookup_type.emplace_back(0); + q_aux.emplace_back(0); +} + +// range constraint a value by decomposing it into limbs whose size should be the default range constraint size +std::vector UltraCircuitConstructor::decompose_into_default_range_better_for_oddlimbnum( + const uint32_t variable_index, const size_t num_bits, std::string const& msg) +{ + std::vector sums; + const size_t limb_num = (size_t)num_bits / DEFAULT_PLOOKUP_RANGE_BITNUM; + const size_t last_limb_size = num_bits - (limb_num * DEFAULT_PLOOKUP_RANGE_BITNUM); + if (limb_num < 3) { + std::cerr + << "number of bits in range must be an integer multipe of DEFAULT_PLOOKUP_RANGE_BITNUM of size at least 3" + << std::endl; + return sums; + } + + const uint256_t val = (uint256_t)(get_variable(variable_index)); + // check witness value is indeed in range (commented out cause interferes with negative tests) + // ASSERT(val < ((uint256_t)1 << num_bits) - 1); // Q:ask Zac what happens with wrapping when converting fr to + // uint256 + // ASSERT(limb_num % 3 == 0); // TODO: write version of method that doesn't need this + std::vector val_limbs; + std::vector val_slices; + for (size_t i = 0; i < limb_num; i++) { + val_slices.emplace_back( + barretenberg::fr(val.slice(DEFAULT_PLOOKUP_RANGE_BITNUM * i, DEFAULT_PLOOKUP_RANGE_BITNUM * (i + 1) - 1))); + val_limbs.emplace_back(add_variable(val_slices[i])); + create_new_range_constraint(val_limbs[i], DEFAULT_PLOOKUP_RANGE_SIZE); + } + + uint64_t last_limb_range = ((uint64_t)1 << last_limb_size) - 1; + fr last_slice(0); + uint32_t last_limb(zero_idx); + size_t total_limb_num = limb_num; + if (last_limb_size > 0) { + val_slices.emplace_back(fr(val.slice(num_bits - last_limb_size, num_bits))); + val_limbs.emplace_back(add_variable(last_slice)); + create_new_range_constraint(last_limb, last_limb_range); + total_limb_num++; + } + // pad slices and limbs in case they are not 2 mod 3 + if (total_limb_num % 3 == 1) { + val_limbs.emplace_back(zero_idx); // TODO: check this is zero + val_slices.emplace_back(0); + total_limb_num++; + } + fr shift = fr(1 << DEFAULT_PLOOKUP_RANGE_BITNUM); + fr second_shift = shift * shift; + sums.emplace_back(add_variable(val_slices[0] + shift * val_slices[1] + second_shift * val_slices[2])); + create_big_add_gate({ val_limbs[0], val_limbs[1], val_limbs[2], sums[0], 1, shift, second_shift, -1, 0 }); + fr cur_shift = (shift * second_shift); + fr cur_second_shift = cur_shift * shift; + for (size_t i = 3; i < total_limb_num; i = i + 2) { + sums.emplace_back(add_variable(get_variable(sums[sums.size() - 1]) + cur_shift * val_slices[i] + + cur_second_shift * val_slices[i + 1])); + create_big_add_gate({ sums[sums.size() - 2], + val_limbs[i], + val_limbs[i + 1], + sums[sums.size() - 1], + 1, + cur_shift, + cur_second_shift, + -1, + 0 }); + cur_shift *= second_shift; + cur_second_shift *= second_shift; + } + assert_equal(sums[sums.size() - 1], variable_index, msg); + return sums; +} + +/** + * @brief Enable the auxilary gate of particular type + * + * @details If we have several operations being performed do not require parametrization + * (if we put each of them into a separate widget they would not require any selectors other than the ones enabling the + * operation itself, for example q_special*(w_l-2*w_r)), we can group them all into one widget, by using a special + * selector q_aux for all of them and enabling each in particular, depending on the combination of standard selector + * values. So you can do: + * q_aux * (q_1 * q_2 * statement_1 + q_3 * q_4 * statement_2). q_1=q_2=1 would activate statement_1, while q_3=q_4=1 + * would activate statement_2 + * + * * 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 | + * + * @param type + */ +void UltraCircuitConstructor::apply_aux_selectors(const AUX_SELECTORS type) +{ + q_aux.emplace_back(type == AUX_SELECTORS::NONE ? 0 : 1); + q_sort.emplace_back(0); + q_lookup_type.emplace_back(0); + q_elliptic.emplace_back(0); + switch (type) { + case AUX_SELECTORS::LIMB_ACCUMULATE_1: { + q_1.emplace_back(0); + q_2.emplace_back(0); + q_3.emplace_back(1); + q_4.emplace_back(1); + q_m.emplace_back(0); + q_c.emplace_back(0); + q_arith.emplace_back(0); + break; + } + case AUX_SELECTORS::LIMB_ACCUMULATE_2: { + q_1.emplace_back(0); + q_2.emplace_back(0); + q_3.emplace_back(1); + q_4.emplace_back(0); + q_m.emplace_back(1); + q_c.emplace_back(0); + q_arith.emplace_back(0); + break; + } + case AUX_SELECTORS::NON_NATIVE_FIELD_1: { + q_1.emplace_back(0); + q_2.emplace_back(1); + q_3.emplace_back(1); + q_4.emplace_back(0); + q_m.emplace_back(0); + q_c.emplace_back(0); + q_arith.emplace_back(0); + break; + } + case AUX_SELECTORS::NON_NATIVE_FIELD_2: { + q_1.emplace_back(0); + q_2.emplace_back(1); + q_3.emplace_back(0); + q_4.emplace_back(1); + q_m.emplace_back(0); + q_c.emplace_back(0); + q_arith.emplace_back(0); + break; + } + case AUX_SELECTORS::NON_NATIVE_FIELD_3: { + q_1.emplace_back(0); + q_2.emplace_back(1); + q_3.emplace_back(0); + q_4.emplace_back(0); + q_m.emplace_back(1); + q_c.emplace_back(0); + q_arith.emplace_back(0); + break; + } + case AUX_SELECTORS::ROM_CONSISTENCY_CHECK: { + // Memory read gate used with the sorted list of memory reads. + // Apply sorted memory read checks with the following additional check: + // 1. Assert that if index field across two gates does not change, the value field does not change. + // Used for ROM reads and RAM reads across write/read boundaries + q_1.emplace_back(1); + q_2.emplace_back(1); + q_3.emplace_back(0); + q_4.emplace_back(0); + q_m.emplace_back(0); + q_c.emplace_back(0); + q_arith.emplace_back(0); + break; + } + case AUX_SELECTORS::RAM_CONSISTENCY_CHECK: { + // Memory read gate used with the sorted list of memory reads. + // 1. Validate adjacent index values across 2 gates increases by 0 or 1 + // 2. Validate record computation (r = read_write_flag + index * \eta + \timestamp * \eta^2 + value * \eta^3) + // 3. If adjacent index values across 2 gates does not change, and the next gate's read_write_flag is set to + // 'read', validate adjacent values do not change Used for ROM reads and RAM reads across read/write boundaries + q_1.emplace_back(0); + q_2.emplace_back(0); + q_3.emplace_back(0); + q_4.emplace_back(0); + q_m.emplace_back(0); + q_c.emplace_back(0); + q_arith.emplace_back(1); + break; + } + case AUX_SELECTORS::RAM_TIMESTAMP_CHECK: { + // For two adjacent RAM entries that share the same index, validate the timestamp value is monotonically + // increasing + q_1.emplace_back(1); + q_2.emplace_back(0); + q_3.emplace_back(0); + q_4.emplace_back(1); + q_m.emplace_back(0); + q_c.emplace_back(0); + q_arith.emplace_back(0); + break; + } + case AUX_SELECTORS::ROM_READ: { + // Memory read gate for reading memory cells. + // Validates record witness computation (r = read_write_flag + index * \eta + timestamp * \eta^2 + value * + // \eta^3) + q_1.emplace_back(1); + q_2.emplace_back(0); + q_3.emplace_back(0); + q_4.emplace_back(0); + q_m.emplace_back(1); // validate record witness is correctly computed + q_c.emplace_back(0); // read/write flag stored in q_c + q_arith.emplace_back(0); + break; + } + case AUX_SELECTORS::RAM_READ: { + // Memory read gate for reading memory cells. + // Validates record witness computation (r = read_write_flag + index * \eta + timestamp * \eta^2 + value * + // \eta^3) + q_1.emplace_back(1); + q_2.emplace_back(0); + q_3.emplace_back(0); + q_4.emplace_back(0); + q_m.emplace_back(1); // validate record witness is correctly computed + q_c.emplace_back(0); // read/write flag stored in q_c + q_arith.emplace_back(0); + break; + } + case AUX_SELECTORS::RAM_WRITE: { + // Memory read gate for writing memory cells. + // Validates record witness computation (r = read_write_flag + index * \eta + timestamp * \eta^2 + value * + // \eta^3) + q_1.emplace_back(1); + q_2.emplace_back(0); + q_3.emplace_back(0); + q_4.emplace_back(0); + q_m.emplace_back(1); // validate record witness is correctly computed + q_c.emplace_back(1); // read/write flag stored in q_c + q_arith.emplace_back(0); + break; + } + default: { + q_1.emplace_back(0); + q_2.emplace_back(0); + q_3.emplace_back(0); + q_4.emplace_back(0); + q_m.emplace_back(0); + q_c.emplace_back(0); + q_arith.emplace_back(0); + break; + } + } +} + +/** + * NON NATIVE FIELD METHODS + * + * Methods to efficiently apply constraints that evaluate non-native field multiplications + **/ + +/** + * Applies range constraints to two 70-bit limbs, splititng each into 5 14-bit sublimbs. + * We can efficiently chain together two 70-bit limb checks in 3 gates, using auxiliary gates + **/ +void UltraCircuitConstructor::range_constrain_two_limbs(const uint32_t lo_idx, + const uint32_t hi_idx, + const size_t lo_limb_bits, + const size_t hi_limb_bits) +{ + // Validate limbs are <= 70 bits. If limbs are larger we require more witnesses and cannot use our limb accumulation + // custom gate + ASSERT(lo_limb_bits <= (14 * 5)); + ASSERT(hi_limb_bits <= (14 * 5)); + + // Sometimes we try to use limbs that are too large. It's easier to catch this issue here + const auto get_sublimbs = [&](const uint32_t& limb_idx, const std::array& sublimb_masks) { + const uint256_t limb = get_variable(limb_idx); + // we can use constant 2^14 - 1 mask here. If the sublimb value exceeds the expected value then witness will + // fail the range check below + // We also use zero_idx to substitute variables that should be zero + constexpr uint256_t MAX_SUBLIMB_MASK = (uint256_t(1) << 14) - 1; + std::array sublimb_indices; + sublimb_indices[0] = sublimb_masks[0] != 0 ? add_variable(limb & MAX_SUBLIMB_MASK) : zero_idx; + sublimb_indices[1] = sublimb_masks[1] != 0 ? add_variable((limb >> 14) & MAX_SUBLIMB_MASK) : zero_idx; + sublimb_indices[2] = sublimb_masks[2] != 0 ? add_variable((limb >> 28) & MAX_SUBLIMB_MASK) : zero_idx; + sublimb_indices[3] = sublimb_masks[3] != 0 ? add_variable((limb >> 42) & MAX_SUBLIMB_MASK) : zero_idx; + sublimb_indices[4] = sublimb_masks[4] != 0 ? add_variable((limb >> 56) & MAX_SUBLIMB_MASK) : zero_idx; + return sublimb_indices; + }; + + const auto get_limb_masks = [](size_t limb_bits) { + std::array sublimb_masks; + sublimb_masks[0] = limb_bits >= 14 ? 14 : limb_bits; + sublimb_masks[1] = limb_bits >= 28 ? 14 : (limb_bits > 14 ? limb_bits - 14 : 0); + sublimb_masks[2] = limb_bits >= 42 ? 14 : (limb_bits > 28 ? limb_bits - 28 : 0); + sublimb_masks[3] = limb_bits >= 56 ? 14 : (limb_bits > 42 ? limb_bits - 42 : 0); + sublimb_masks[4] = (limb_bits > 56 ? limb_bits - 56 : 0); + + for (auto& mask : sublimb_masks) { + mask = (1ULL << mask) - 1ULL; + } + return sublimb_masks; + }; + + const auto lo_masks = get_limb_masks(lo_limb_bits); + const auto hi_masks = get_limb_masks(hi_limb_bits); + const std::array lo_sublimbs = get_sublimbs(lo_idx, lo_masks); + const std::array hi_sublimbs = get_sublimbs(hi_idx, hi_masks); + + w_l.emplace_back(lo_sublimbs[0]); + w_r.emplace_back(lo_sublimbs[1]); + w_o.emplace_back(lo_sublimbs[2]); + w_4.emplace_back(lo_idx); + + w_l.emplace_back(lo_sublimbs[3]); + w_r.emplace_back(lo_sublimbs[4]); + w_o.emplace_back(hi_sublimbs[0]); + w_4.emplace_back(hi_sublimbs[1]); + + w_l.emplace_back(hi_sublimbs[2]); + w_r.emplace_back(hi_sublimbs[3]); + w_o.emplace_back(hi_sublimbs[4]); + w_4.emplace_back(hi_idx); + + apply_aux_selectors(AUX_SELECTORS::LIMB_ACCUMULATE_1); + apply_aux_selectors(AUX_SELECTORS::LIMB_ACCUMULATE_2); + apply_aux_selectors(AUX_SELECTORS::NONE); + num_gates += 3; + + for (size_t i = 0; i < 5; i++) { + if (lo_masks[i] != 0) { + create_new_range_constraint(lo_sublimbs[i], lo_masks[i]); + } + if (hi_masks[i] != 0) { + create_new_range_constraint(hi_sublimbs[i], hi_masks[i]); + } + } +}; + +/** + * @brief Decompose a single witness into two, where the lowest is DEFAULT_NON_NATIVE_FIELD_LIMB_BITS (68) range + * constrained and the lowst is num_limb_bits - DEFAULT.. range constrained. + * + * @details Doesn't create gates constraining the limbs to each other. + * + * @param limb_idx The index of the limb that will be decomposed + * @param num_limb_bits The range we want to constrain the original limb to + * @return std::array The indices of new limbs. + */ +std::array UltraCircuitConstructor::decompose_non_native_field_double_width_limb( + const uint32_t limb_idx, const size_t num_limb_bits) +{ + ASSERT(uint256_t(get_variable_reference(limb_idx)) < (uint256_t(1) << num_limb_bits)); + constexpr barretenberg::fr LIMB_MASK = (uint256_t(1) << DEFAULT_NON_NATIVE_FIELD_LIMB_BITS) - 1; + 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; + ASSERT(low + (hi << DEFAULT_NON_NATIVE_FIELD_LIMB_BITS) == value); + + const uint32_t low_idx = add_variable(low); + const uint32_t hi_idx = add_variable(hi); + + ASSERT(num_limb_bits > DEFAULT_NON_NATIVE_FIELD_LIMB_BITS); + const size_t lo_bits = DEFAULT_NON_NATIVE_FIELD_LIMB_BITS; + const size_t hi_bits = num_limb_bits - DEFAULT_NON_NATIVE_FIELD_LIMB_BITS; + range_constrain_two_limbs(low_idx, hi_idx, lo_bits, hi_bits); + + return std::array{ low_idx, hi_idx }; +} + +/** + * NON NATIVE FIELD MULTIPLICATION CUSTOM GATE SEQUENCE + * + * This method will evaluate the equation (a * b = q * p + r) + * Where a, b, q, r are all emulated non-native field elements that are each split across 4 distinct witness variables + * + * The non-native field modulus, p, is a circuit constant + * + * The return value are the witness indices of the two remainder limbs `lo_1, hi_2` + * + * N.B. this method does NOT evaluate the prime field component of non-native field multiplications + **/ +std::array UltraCircuitConstructor::evaluate_non_native_field_multiplication( + const non_native_field_witnesses& input, const bool range_constrain_quotient_and_remainder) +{ + + std::array a{ + get_variable(input.a[0]), + get_variable(input.a[1]), + get_variable(input.a[2]), + get_variable(input.a[3]), + }; + std::array b{ + get_variable(input.b[0]), + get_variable(input.b[1]), + get_variable(input.b[2]), + get_variable(input.b[3]), + }; + std::array q{ + get_variable(input.q[0]), + get_variable(input.q[1]), + get_variable(input.q[2]), + get_variable(input.q[3]), + }; + std::array r{ + get_variable(input.r[0]), + get_variable(input.r[1]), + get_variable(input.r[2]), + get_variable(input.r[3]), + }; + + constexpr barretenberg::fr LIMB_SHIFT = uint256_t(1) << DEFAULT_NON_NATIVE_FIELD_LIMB_BITS; + constexpr barretenberg::fr LIMB_SHIFT_2 = uint256_t(1) << (2 * DEFAULT_NON_NATIVE_FIELD_LIMB_BITS); + constexpr barretenberg::fr LIMB_SHIFT_3 = uint256_t(1) << (3 * DEFAULT_NON_NATIVE_FIELD_LIMB_BITS); + constexpr barretenberg::fr LIMB_RSHIFT = + barretenberg::fr(1) / barretenberg::fr(uint256_t(1) << DEFAULT_NON_NATIVE_FIELD_LIMB_BITS); + constexpr barretenberg::fr LIMB_RSHIFT_2 = + barretenberg::fr(1) / barretenberg::fr(uint256_t(1) << (2 * DEFAULT_NON_NATIVE_FIELD_LIMB_BITS)); + + barretenberg::fr lo_0 = a[0] * b[0] - r[0] + (a[1] * b[0] + a[0] * b[1]) * LIMB_SHIFT; + barretenberg::fr lo_1 = (lo_0 + q[0] * input.neg_modulus[0] + + (q[1] * input.neg_modulus[0] + q[0] * input.neg_modulus[1] - r[1]) * LIMB_SHIFT) * + LIMB_RSHIFT_2; + + barretenberg::fr hi_0 = a[2] * b[0] + a[0] * b[2] + (a[0] * b[3] + a[3] * b[0] - r[3]) * LIMB_SHIFT; + barretenberg::fr hi_1 = hi_0 + a[1] * b[1] - r[2] + (a[1] * b[2] + a[2] * b[1]) * LIMB_SHIFT; + barretenberg::fr hi_2 = (hi_1 + lo_1 + q[2] * input.neg_modulus[0] + + (q[3] * input.neg_modulus[0] + q[2] * input.neg_modulus[1]) * LIMB_SHIFT); + barretenberg::fr hi_3 = (hi_2 + (q[0] * input.neg_modulus[3] + q[1] * input.neg_modulus[2]) * LIMB_SHIFT + + (q[0] * input.neg_modulus[2] + q[1] * input.neg_modulus[1])) * + LIMB_RSHIFT_2; + + const uint32_t lo_0_idx = add_variable(lo_0); + const uint32_t lo_1_idx = add_variable(lo_1); + const uint32_t hi_0_idx = add_variable(hi_0); + const uint32_t hi_1_idx = add_variable(hi_1); + const uint32_t hi_2_idx = add_variable(hi_2); + const uint32_t hi_3_idx = add_variable(hi_3); + + // Sometimes we have already applied range constraints on the quotient and remainder + if (range_constrain_quotient_and_remainder) { + // /** + // * r_prime - r_0 - r_1 * 2^b - r_2 * 2^2b - r_3 * 2^3b = 0 + // * + // * w_4_omega - w_4 + w_1(2^b) + w_2(2^2b) + w_3(2^3b) = 0 + // * + // **/ + create_big_add_gate( + { input.r[1], input.r[2], input.r[3], input.r[4], LIMB_SHIFT, LIMB_SHIFT_2, LIMB_SHIFT_3, -1, 0 }, true); + range_constrain_two_limbs(input.r[0], input.r[1]); + range_constrain_two_limbs(input.r[2], input.r[3]); + + // /** + // * q_prime - q_0 - q_1 * 2^b - q_2 * 2^2b - q_3 * 2^3b = 0 + // * + // * w_4_omega - w_4 + w_1(2^b) + w_2(2^2b) + w_3(2^3b) = 0 + // * + // **/ + create_big_add_gate( + { input.q[1], input.q[2], input.q[3], input.q[4], LIMB_SHIFT, LIMB_SHIFT_2, LIMB_SHIFT_3, -1, 0 }, true); + range_constrain_two_limbs(input.q[0], input.q[1]); + range_constrain_two_limbs(input.q[2], input.q[3]); + } + + // product gate 1 + // (lo_0 + q_0(p_0 + p_1*2^b) + q_1(p_0*2^b) - (r_1)2^b)2^-2b - lo_1 = 0 + create_big_add_gate({ input.q[0], + input.q[1], + input.r[1], + lo_1_idx, + input.neg_modulus[0] + input.neg_modulus[1] * LIMB_SHIFT, + input.neg_modulus[0] * LIMB_SHIFT, + -LIMB_SHIFT, + -LIMB_SHIFT.sqr(), + 0 }, + true); + + w_l.emplace_back(input.a[1]); + w_r.emplace_back(input.b[1]); + w_o.emplace_back(input.r[0]); + w_4.emplace_back(lo_0_idx); + apply_aux_selectors(AUX_SELECTORS::NON_NATIVE_FIELD_1); + ++num_gates; + w_l.emplace_back(input.a[0]); + w_r.emplace_back(input.b[0]); + w_o.emplace_back(input.a[3]); + w_4.emplace_back(input.b[3]); + apply_aux_selectors(AUX_SELECTORS::NON_NATIVE_FIELD_2); + ++num_gates; + w_l.emplace_back(input.a[2]); + w_r.emplace_back(input.b[2]); + w_o.emplace_back(input.r[3]); + w_4.emplace_back(hi_0_idx); + apply_aux_selectors(AUX_SELECTORS::NON_NATIVE_FIELD_3); + ++num_gates; + w_l.emplace_back(input.a[1]); + w_r.emplace_back(input.b[1]); + w_o.emplace_back(input.r[2]); + w_4.emplace_back(hi_1_idx); + apply_aux_selectors(AUX_SELECTORS::NONE); + ++num_gates; + + /** + * product gate 6 + * + * hi_2 - hi_1 - lo_1 - q[2](p[1].2^b + p[0]) - q[3](p[0].2^b) = 0 + * + **/ + create_big_add_gate( + { + input.q[2], + input.q[3], + lo_1_idx, + hi_1_idx, + -input.neg_modulus[1] * LIMB_SHIFT - input.neg_modulus[0], + -input.neg_modulus[0] * LIMB_SHIFT, + -1, + -1, + 0, + }, + true); + + /** + * product gate 7 + * + * hi_3 - (hi_2 - q[0](p[3].2^b + p[2]) - q[1](p[2].2^b + p[1])).2^-2b + **/ + create_big_add_gate({ + hi_3_idx, + input.q[0], + input.q[1], + hi_2_idx, + -1, + input.neg_modulus[3] * LIMB_RSHIFT + input.neg_modulus[2] * LIMB_RSHIFT_2, + input.neg_modulus[2] * LIMB_RSHIFT + input.neg_modulus[1] * LIMB_RSHIFT_2, + LIMB_RSHIFT_2, + 0, + }); + + return std::array{ lo_1_idx, hi_3_idx }; +} + +/** + * Compute the limb-multiplication part of a non native field mul + * + * i.e. compute the low 204 and high 204 bit components of `a * b` where `a, b` are nnf elements composed of 4 + * limbs with size DEFAULT_NON_NATIVE_FIELD_LIMB_BITS + * + **/ +std::array UltraCircuitConstructor::evaluate_partial_non_native_field_multiplication( + const non_native_field_witnesses& input) +{ + + std::array a{ + get_variable(input.a[0]), + get_variable(input.a[1]), + get_variable(input.a[2]), + get_variable(input.a[3]), + }; + std::array b{ + get_variable(input.b[0]), + get_variable(input.b[1]), + get_variable(input.b[2]), + get_variable(input.b[3]), + }; + + constexpr barretenberg::fr LIMB_SHIFT = uint256_t(1) << DEFAULT_NON_NATIVE_FIELD_LIMB_BITS; + + barretenberg::fr lo_0 = a[0] * b[0] + (a[1] * b[0] + a[0] * b[1]) * LIMB_SHIFT; + + barretenberg::fr hi_0 = a[2] * b[0] + a[0] * b[2] + (a[0] * b[3] + a[3] * b[0]) * LIMB_SHIFT; + barretenberg::fr hi_1 = hi_0 + a[1] * b[1] + (a[1] * b[2] + a[2] * b[1]) * LIMB_SHIFT; + + const uint32_t lo_0_idx = add_variable(lo_0); + const uint32_t hi_0_idx = add_variable(hi_0); + const uint32_t hi_1_idx = add_variable(hi_1); + + w_l.emplace_back(input.a[1]); + w_r.emplace_back(input.b[1]); + w_o.emplace_back(zero_idx); + w_4.emplace_back(lo_0_idx); + apply_aux_selectors(AUX_SELECTORS::NON_NATIVE_FIELD_1); + ++num_gates; + w_l.emplace_back(input.a[0]); + w_r.emplace_back(input.b[0]); + w_o.emplace_back(input.a[3]); + w_4.emplace_back(input.b[3]); + apply_aux_selectors(AUX_SELECTORS::NON_NATIVE_FIELD_2); + ++num_gates; + w_l.emplace_back(input.a[2]); + w_r.emplace_back(input.b[2]); + w_o.emplace_back(zero_idx); + w_4.emplace_back(hi_0_idx); + apply_aux_selectors(AUX_SELECTORS::NON_NATIVE_FIELD_3); + ++num_gates; + w_l.emplace_back(input.a[1]); + w_r.emplace_back(input.b[1]); + w_o.emplace_back(zero_idx); + w_4.emplace_back(hi_1_idx); + apply_aux_selectors(AUX_SELECTORS::NONE); + ++num_gates; + return std::array{ lo_0_idx, hi_1_idx }; +} + +/** + * Uses a sneaky extra mini-addition gate in `plookup_arithmetic_widget.hpp` to add two non-native + * field elements in 4 gates (would normally take 5) + **/ +std::array UltraCircuitConstructor::evaluate_non_native_field_addition( + add_simple limb0, + add_simple limb1, + add_simple limb2, + add_simple limb3, + std::tuple limbp) +{ + const auto& x_0 = std::get<0>(limb0).first; + const auto& x_1 = std::get<0>(limb1).first; + const auto& x_2 = std::get<0>(limb2).first; + const auto& x_3 = std::get<0>(limb3).first; + const auto& x_p = std::get<0>(limbp); + + const auto& x_mulconst0 = std::get<0>(limb0).second; + const auto& x_mulconst1 = std::get<0>(limb1).second; + const auto& x_mulconst2 = std::get<0>(limb2).second; + const auto& x_mulconst3 = std::get<0>(limb3).second; + + const auto& y_0 = std::get<1>(limb0).first; + const auto& y_1 = std::get<1>(limb1).first; + const auto& y_2 = std::get<1>(limb2).first; + const auto& y_3 = std::get<1>(limb3).first; + const auto& y_p = std::get<1>(limbp); + + const auto& y_mulconst0 = std::get<1>(limb0).second; + const auto& y_mulconst1 = std::get<1>(limb1).second; + const auto& y_mulconst2 = std::get<1>(limb2).second; + const auto& y_mulconst3 = std::get<1>(limb3).second; + + // constant additive terms + const auto& addconst0 = std::get<2>(limb0); + const auto& addconst1 = std::get<2>(limb1); + const auto& addconst2 = std::get<2>(limb2); + const auto& addconst3 = std::get<2>(limb3); + const auto& addconstp = std::get<2>(limbp); + + // get value of result limbs + const auto z_0value = get_variable(x_0) * x_mulconst0 + get_variable(y_0) * y_mulconst0 + addconst0; + const auto z_1value = get_variable(x_1) * x_mulconst1 + get_variable(y_1) * y_mulconst1 + addconst1; + const auto z_2value = get_variable(x_2) * x_mulconst2 + get_variable(y_2) * y_mulconst2 + addconst2; + const auto z_3value = get_variable(x_3) * x_mulconst3 + get_variable(y_3) * y_mulconst3 + addconst3; + const auto z_pvalue = get_variable(x_p) + get_variable(y_p) + addconstp; + + const auto z_0 = add_variable(z_0value); + const auto z_1 = add_variable(z_1value); + const auto z_2 = add_variable(z_2value); + const auto z_3 = add_variable(z_3value); + const auto z_p = add_variable(z_pvalue); + + /** + * we want the following layout in program memory + * (x - y = z) + * + * | 1 | 2 | 3 | 4 | + * |-----|-----|-----|-----| + * | y.p | x.0 | y.0 | x.p | (b.p + c.p - a.p = 0) AND (a.0 - b.0 - c.0 = 0) + * | z.p | x.1 | y.1 | z.0 | (a.1 - b.1 - c.1 = 0) + * | x.2 | y.2 | z.2 | z.1 | (a.2 - b.2 - c.2 = 0) + * | x.3 | y.3 | z.3 | --- | (a.3 - b.3 - c.3 = 0) + * + * By setting `q_arith` to `3`, we can validate `x_p + y_p + q_m = z_p` + **/ + // GATE 1 + w_l.emplace_back(y_p); + w_r.emplace_back(x_0); + w_o.emplace_back(y_0); + w_4.emplace_back(x_p); + w_l.emplace_back(z_p); + w_r.emplace_back(x_1); + w_o.emplace_back(y_1); // | 1 | 2 | 3 | 4 | + w_4.emplace_back(z_0); // |-----|-----|-----|-----| + w_l.emplace_back(x_2); // | y.p | x.0 | y.0 | z.p | (b.p + b.p - c.p = 0) AND (a.0 + b.0 - c.0 = 0) + w_r.emplace_back(y_2); // | x.p | x.1 | y.1 | z.0 | (a.1 + b.1 - c.1 = 0) + w_o.emplace_back(z_2); // | x.2 | y.2 | z.2 | z.1 | (a.2 + b.2 - c.2 = 0) + w_4.emplace_back(z_1); // | x.3 | y.3 | z.3 | --- | (a.3 + b.3 - c.3 = 0) + w_l.emplace_back(x_3); + w_r.emplace_back(y_3); + w_o.emplace_back(z_3); + w_4.emplace_back(zero_idx); + + q_m.emplace_back(addconstp); + q_1.emplace_back(0); + q_2.emplace_back(-x_mulconst0 * + 2); // scale constants by 2. If q_arith = 3 then w_4_omega value (z0) gets scaled by 2x + q_3.emplace_back(-y_mulconst0 * 2); // z_0 - (x_0 * -xmulconst0) - (y_0 * ymulconst0) = 0 => z_0 = x_0 + y_0 + q_4.emplace_back(0); + q_c.emplace_back(-addconst0 * 2); + q_arith.emplace_back(3); + + q_m.emplace_back(0); + q_1.emplace_back(0); + q_2.emplace_back(-x_mulconst1); + q_3.emplace_back(-y_mulconst1); + q_4.emplace_back(0); + q_c.emplace_back(-addconst1); + q_arith.emplace_back(2); + + q_m.emplace_back(0); + q_1.emplace_back(-x_mulconst2); + q_2.emplace_back(-y_mulconst2); + q_3.emplace_back(1); + q_4.emplace_back(0); + q_c.emplace_back(-addconst2); + q_arith.emplace_back(1); + + q_m.emplace_back(0); + q_1.emplace_back(-x_mulconst3); + q_2.emplace_back(-y_mulconst3); + q_3.emplace_back(1); + q_4.emplace_back(0); + q_c.emplace_back(-addconst3); + q_arith.emplace_back(1); + + for (size_t i = 0; i < 4; ++i) { + q_sort.emplace_back(0); + q_lookup_type.emplace_back(0); + q_elliptic.emplace_back(0); + q_aux.emplace_back(0); + } + + num_gates += 4; + return std::array{ + z_0, z_1, z_2, z_3, z_p, + }; +} + +std::array UltraCircuitConstructor::evaluate_non_native_field_subtraction( + add_simple limb0, + add_simple limb1, + add_simple limb2, + add_simple limb3, + std::tuple limbp) +{ + const auto& x_0 = std::get<0>(limb0).first; + const auto& x_1 = std::get<0>(limb1).first; + const auto& x_2 = std::get<0>(limb2).first; + const auto& x_3 = std::get<0>(limb3).first; + const auto& x_p = std::get<0>(limbp); + + const auto& x_mulconst0 = std::get<0>(limb0).second; + const auto& x_mulconst1 = std::get<0>(limb1).second; + const auto& x_mulconst2 = std::get<0>(limb2).second; + const auto& x_mulconst3 = std::get<0>(limb3).second; + + const auto& y_0 = std::get<1>(limb0).first; + const auto& y_1 = std::get<1>(limb1).first; + const auto& y_2 = std::get<1>(limb2).first; + const auto& y_3 = std::get<1>(limb3).first; + const auto& y_p = std::get<1>(limbp); + + const auto& y_mulconst0 = std::get<1>(limb0).second; + const auto& y_mulconst1 = std::get<1>(limb1).second; + const auto& y_mulconst2 = std::get<1>(limb2).second; + const auto& y_mulconst3 = std::get<1>(limb3).second; + + // constant additive terms + const auto& addconst0 = std::get<2>(limb0); + const auto& addconst1 = std::get<2>(limb1); + const auto& addconst2 = std::get<2>(limb2); + const auto& addconst3 = std::get<2>(limb3); + const auto& addconstp = std::get<2>(limbp); + + // get value of result limbs + const auto z_0value = get_variable(x_0) * x_mulconst0 - get_variable(y_0) * y_mulconst0 + addconst0; + const auto z_1value = get_variable(x_1) * x_mulconst1 - get_variable(y_1) * y_mulconst1 + addconst1; + const auto z_2value = get_variable(x_2) * x_mulconst2 - get_variable(y_2) * y_mulconst2 + addconst2; + const auto z_3value = get_variable(x_3) * x_mulconst3 - get_variable(y_3) * y_mulconst3 + addconst3; + const auto z_pvalue = get_variable(x_p) - get_variable(y_p) + addconstp; + + const auto z_0 = add_variable(z_0value); + const auto z_1 = add_variable(z_1value); + const auto z_2 = add_variable(z_2value); + const auto z_3 = add_variable(z_3value); + const auto z_p = add_variable(z_pvalue); + + /** + * we want the following layout in program memory + * (x - y = z) + * + * | 1 | 2 | 3 | 4 | + * |-----|-----|-----|-----| + * | y.p | x.0 | y.0 | z.p | (b.p + c.p - a.p = 0) AND (a.0 - b.0 - c.0 = 0) + * | x.p | x.1 | y.1 | z.0 | (a.1 - b.1 - c.1 = 0) + * | x.2 | y.2 | z.2 | z.1 | (a.2 - b.2 - c.2 = 0) + * | x.3 | y.3 | z.3 | --- | (a.3 - b.3 - c.3 = 0) + * + **/ + // GATE 1 + w_l.emplace_back(y_p); + w_r.emplace_back(x_0); + w_o.emplace_back(y_0); + w_4.emplace_back(z_p); + w_l.emplace_back(x_p); + w_r.emplace_back(x_1); + w_o.emplace_back(y_1); // | 1 | 2 | 3 | 4 | + w_4.emplace_back(z_0); // |-----|-----|-----|-----| + w_l.emplace_back(x_2); // | y.p | x.0 | y.0 | z.p | (b.p + c.p - a.p = 0) AND (a.0 - b.0 - c.0 = 0) + w_r.emplace_back(y_2); // | x.p | x.1 | y.1 | z.0 | (a.1 - b.1 - c.1 = 0) + w_o.emplace_back(z_2); // | x.2 | y.2 | z.2 | z.1 | (a.2 - b.2 - c.2 = 0) + w_4.emplace_back(z_1); // | x.3 | y.3 | z.3 | --- | (a.3 - b.3 - c.3 = 0) + w_l.emplace_back(x_3); + w_r.emplace_back(y_3); + w_o.emplace_back(z_3); + w_4.emplace_back(zero_idx); + + q_m.emplace_back(-addconstp); + q_1.emplace_back(0); + q_2.emplace_back(-x_mulconst0 * 2); + q_3.emplace_back(y_mulconst0 * 2); // z_0 + (x_0 * -xmulconst0) + (y_0 * ymulconst0) = 0 => z_0 = x_0 - y_0 + q_4.emplace_back(0); + q_c.emplace_back(-addconst0 * 2); + q_arith.emplace_back(3); + + q_m.emplace_back(0); + q_1.emplace_back(0); + q_2.emplace_back(-x_mulconst1); + q_3.emplace_back(y_mulconst1); + q_4.emplace_back(0); + q_c.emplace_back(-addconst1); + q_arith.emplace_back(2); + + q_m.emplace_back(0); + q_1.emplace_back(-x_mulconst2); + q_2.emplace_back(y_mulconst2); + q_3.emplace_back(1); + q_4.emplace_back(0); + q_c.emplace_back(-addconst2); + q_arith.emplace_back(1); + + q_m.emplace_back(0); + q_1.emplace_back(-x_mulconst3); + q_2.emplace_back(y_mulconst3); + q_3.emplace_back(1); + q_4.emplace_back(0); + q_c.emplace_back(-addconst3); + q_arith.emplace_back(1); + + for (size_t i = 0; i < 4; ++i) { + q_sort.emplace_back(0); + q_lookup_type.emplace_back(0); + q_elliptic.emplace_back(0); + q_aux.emplace_back(0); + } + + num_gates += 4; + return std::array{ + z_0, z_1, z_2, z_3, z_p, + }; +} + +/** + * @brief Gate that 'reads' from a ROM table. + * i.e. table index is a witness not precomputed + * + * @param record Stores details of this read operation. Mutated by this fn! + */ +void UltraCircuitConstructor::create_ROM_gate(RomRecord& record) +{ + // Record wire value can't yet be computed + record.record_witness = add_variable(0); + apply_aux_selectors(AUX_SELECTORS::ROM_READ); + w_l.emplace_back(record.index_witness); + 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; +} + +/** + * @brief Gate that performs consistency checks to validate that a claimed ROM read value is correct + * + * @details sorted ROM gates are generated sequentially, each ROM record is sorted by index + * + * @param record Stores details of this read operation. Mutated by this fn! + */ +void UltraCircuitConstructor::create_sorted_ROM_gate(RomRecord& record) +{ + record.record_witness = add_variable(0); + apply_aux_selectors(AUX_SELECTORS::ROM_CONSISTENCY_CHECK); + w_l.emplace_back(record.index_witness); + 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; +} + +/** + * @brief Create a new read-only memory region + * + * @details Creates a transcript object, where the inside memory state array is filled with "uninitialized memory" + and + * and empty memory record array. Puts this object into the vector of ROM arrays. + * + * @param array_size The size of region in elements + * @return size_t The index of the element + */ +size_t UltraCircuitConstructor::create_ROM_array(const size_t array_size) +{ + RomTranscript new_transcript; + for (size_t i = 0; i < array_size; ++i) { + new_transcript.state.emplace_back( + std::array{ UNINITIALIZED_MEMORY_RECORD, UNINITIALIZED_MEMORY_RECORD }); + } + rom_arrays.emplace_back(new_transcript); + return rom_arrays.size() - 1; +} + +/** + * @brief Gate that performs a read/write operation into a RAM table. + * i.e. table index is a witness not precomputed + * + * @param record Stores details of this read operation. Mutated by this fn! + */ +void UltraCircuitConstructor::create_RAM_gate(RamRecord& record) +{ + // Record wire value can't yet be computed (uses randomnes generated during proof construction). + // However it needs a distinct witness index, + // we will be applying copy constraints + set membership constraints. + // Later on during proof construction we will compute the record wire value + assign it + record.record_witness = add_variable(0); + apply_aux_selectors(record.access_type == RamRecord::AccessType::READ ? AUX_SELECTORS::RAM_READ + : AUX_SELECTORS::RAM_WRITE); + w_l.emplace_back(record.index_witness); + w_r.emplace_back(record.timestamp_witness); + w_o.emplace_back(record.value_witness); + w_4.emplace_back(record.record_witness); + record.gate_index = num_gates; + ++num_gates; +} + +/** + * @brief Gate that performs consistency checks to validate that a claimed RAM read/write value is correct + * + * @details sorted RAM gates are generated sequentially, each RAM record is sorted first by index then by timestamp + * + * @param record Stores details of this read operation. Mutated by this fn! + */ +void UltraCircuitConstructor::create_sorted_RAM_gate(RamRecord& record) +{ + record.record_witness = add_variable(0); + apply_aux_selectors(AUX_SELECTORS::RAM_CONSISTENCY_CHECK); + w_l.emplace_back(record.index_witness); + w_r.emplace_back(record.timestamp_witness); + w_o.emplace_back(record.value_witness); + w_4.emplace_back(record.record_witness); + record.gate_index = num_gates; + ++num_gates; +} + +/** + * @brief Performs consistency checks to validate that a claimed RAM read/write value is correct. + * Used for the final gate in a list of sorted RAM records + * + * @param record Stores details of this read operation. Mutated by this fn! + */ +void UltraCircuitConstructor::create_final_sorted_RAM_gate(RamRecord& record, const size_t ram_array_size) +{ + record.record_witness = add_variable(0); + record.gate_index = num_gates; + + create_big_add_gate({ + record.index_witness, + record.timestamp_witness, + record.value_witness, + record.record_witness, + 1, + 0, + 0, + 0, + -fr((uint64_t)ram_array_size - 1), + }); +} + +/** + * @brief Create a new updateable memory region + * + * @details Creates a transcript object, where the inside memory state array is filled with "uninitialized memory" + and + * and empty memory record array. Puts this object into the vector of ROM arrays. + * + * @param array_size The size of region in elements + * @return size_t The index of the element + */ +size_t UltraCircuitConstructor::create_RAM_array(const size_t array_size) +{ + RamTranscript new_transcript; + for (size_t i = 0; i < array_size; ++i) { + new_transcript.state.emplace_back(UNINITIALIZED_MEMORY_RECORD); + } + ram_arrays.emplace_back(new_transcript); + return ram_arrays.size() - 1; +} + +/** + * @brief Initialize a RAM cell to equal `value_witness` + * + * @param ram_id The index of the ROM array, which cell we are initializing + * @param index_value The index of the cell within the array (an actual index, not a witness index) + * @param value_witness The index of the witness with the value that should be in the + */ +void UltraCircuitConstructor::init_RAM_element(const size_t ram_id, + const size_t index_value, + const uint32_t value_witness) +{ + ASSERT(ram_arrays.size() > ram_id); + RamTranscript& ram_array = ram_arrays[ram_id]; + const uint32_t index_witness = (index_value == 0) ? zero_idx : put_constant_variable((uint64_t)index_value); + ASSERT(ram_array.state.size() > index_value); + ASSERT(ram_array.state[index_value] == UNINITIALIZED_MEMORY_RECORD); + RamRecord new_record{ .index_witness = index_witness, + .timestamp_witness = put_constant_variable((uint64_t)ram_array.access_count), + .value_witness = value_witness, + .index = static_cast(index_value), // TODO: size_t? + .timestamp = static_cast(ram_array.access_count), + .access_type = RamRecord::AccessType::WRITE, + .record_witness = 0, + .gate_index = 0 }; + ram_array.state[index_value] = value_witness; + ram_array.access_count++; + create_RAM_gate(new_record); + ram_array.records.emplace_back(new_record); +} + +uint32_t UltraCircuitConstructor::read_RAM_array(const size_t ram_id, const uint32_t index_witness) +{ + ASSERT(ram_arrays.size() > ram_id); + RamTranscript& ram_array = ram_arrays[ram_id]; + const uint32_t index = static_cast(uint256_t(get_variable(index_witness))); + ASSERT(ram_array.state.size() > index); + ASSERT(ram_array.state[index] != UNINITIALIZED_MEMORY_RECORD); + const auto value = get_variable(ram_array.state[index]); + const uint32_t value_witness = add_variable(value); + + RamRecord new_record{ .index_witness = index_witness, + .timestamp_witness = put_constant_variable((uint64_t)ram_array.access_count), + .value_witness = value_witness, + .index = index, + .timestamp = static_cast(ram_array.access_count), + .access_type = RamRecord::AccessType::READ, + .record_witness = 0, + .gate_index = 0 }; + create_RAM_gate(new_record); + ram_array.records.emplace_back(new_record); + + // increment ram array's access count + ram_array.access_count++; + + // return witness index of the value in the array + return value_witness; +} + +void UltraCircuitConstructor::write_RAM_array(const size_t ram_id, + const uint32_t index_witness, + const uint32_t value_witness) +{ + ASSERT(ram_arrays.size() > ram_id); + RamTranscript& ram_array = ram_arrays[ram_id]; + const uint32_t index = static_cast(uint256_t(get_variable(index_witness))); + ASSERT(ram_array.state.size() > index); + ASSERT(ram_array.state[index] != UNINITIALIZED_MEMORY_RECORD); + + RamRecord new_record{ .index_witness = index_witness, + .timestamp_witness = put_constant_variable((uint64_t)ram_array.access_count), + .value_witness = value_witness, + .index = index, + .timestamp = static_cast(ram_array.access_count), + .access_type = RamRecord::AccessType::WRITE, + .record_witness = 0, + .gate_index = 0 }; + create_RAM_gate(new_record); + ram_array.records.emplace_back(new_record); + + // increment ram array's access count + ram_array.access_count++; + + // update Composer's current state of RAM array + ram_array.state[index] = value_witness; +} + +/** + * Initialize a ROM cell to equal `value_witness` + * `index_value` is a RAW VALUE that describes the cell index. It is NOT a witness + * When intializing ROM arrays, it is important that the index of the cell is known when compiling the circuit. + * This ensures that, for a given circuit, we know with 100% certainty that EVERY rom cell is initialized + **/ + +/** + * @brief Initialize a rom cell to equal `value_witness` + * + * @param rom_id The index of the ROM array, which cell we are initializing + * @param index_value The index of the cell within the array (an actual index, not a witness index) + * @param value_witness The index of the witness with the value that should be in the + */ +void UltraCircuitConstructor::set_ROM_element(const size_t rom_id, + const size_t index_value, + const uint32_t value_witness) +{ + ASSERT(rom_arrays.size() > rom_id); + RomTranscript& rom_array = rom_arrays[rom_id]; + const uint32_t index_witness = (index_value == 0) ? zero_idx : put_constant_variable((uint64_t)index_value); + ASSERT(rom_array.state.size() > index_value); + ASSERT(rom_array.state[index_value][0] == UNINITIALIZED_MEMORY_RECORD); + /** + * The structure MemoryRecord contains the following members in this order: + * uint32_t index_witness; + * uint32_t timestamp_witness; + * uint32_t value_witness; + * uint32_t index; + * uint32_t timestamp; + * uint32_t record_witness; + * size_t gate_index; + * The second initialization value here is the witness, because in ROM it doesn't matter. We will decouple this + * logic later. + */ + RomRecord new_record{ + .index_witness = index_witness, + .value_column1_witness = value_witness, + .value_column2_witness = zero_idx, + .index = static_cast(index_value), + .record_witness = 0, + .gate_index = 0, + }; + rom_array.state[index_value][0] = value_witness; + rom_array.state[index_value][1] = zero_idx; + create_ROM_gate(new_record); + rom_array.records.emplace_back(new_record); +} + +void UltraCircuitConstructor::set_ROM_element_pair(const size_t rom_id, + const size_t index_value, + const std::array& value_witnesses) +{ + ASSERT(rom_arrays.size() > rom_id); + RomTranscript& rom_array = rom_arrays[rom_id]; + const uint32_t index_witness = (index_value == 0) ? zero_idx : put_constant_variable((uint64_t)index_value); + ASSERT(rom_array.state.size() > index_value); + ASSERT(rom_array.state[index_value][0] == UNINITIALIZED_MEMORY_RECORD); + RomRecord new_record{ + .index_witness = index_witness, + .value_column1_witness = value_witnesses[0], + .value_column2_witness = value_witnesses[1], + .index = static_cast(index_value), + .record_witness = 0, + .gate_index = 0, + }; + rom_array.state[index_value][0] = value_witnesses[0]; + rom_array.state[index_value][1] = value_witnesses[1]; + create_ROM_gate(new_record); + rom_array.records.emplace_back(new_record); +} + +uint32_t UltraCircuitConstructor::read_ROM_array(const size_t rom_id, const uint32_t index_witness) +{ + ASSERT(rom_arrays.size() > rom_id); + RomTranscript& rom_array = rom_arrays[rom_id]; + const uint32_t index = static_cast(uint256_t(get_variable(index_witness))); + ASSERT(rom_array.state.size() > index); + ASSERT(rom_array.state[index][0] != UNINITIALIZED_MEMORY_RECORD); + const auto value = get_variable(rom_array.state[index][0]); + const uint32_t value_witness = add_variable(value); + RomRecord new_record{ + .index_witness = index_witness, + .value_column1_witness = value_witness, + .value_column2_witness = zero_idx, + .index = index, + .record_witness = 0, + .gate_index = 0, + }; + create_ROM_gate(new_record); + rom_array.records.emplace_back(new_record); + + // create_read_gate + return value_witness; +} + +// std::array UltraCircuitConstructor::read_ROM_array_pair(const size_t rom_id, const uint32_t +// index_witness) +// { +// std::array value_witnesses; + +// const uint32_t index = static_cast(uint256_t(get_variable(index_witness))); +// ASSERT(rom_arrays.size() > rom_id); +// RomTranscript& rom_array = rom_arrays[rom_id]; +// ASSERT(rom_array.state.size() > index); +// ASSERT(rom_array.state[index][0] != UNINITIALIZED_MEMORY_RECORD); +// ASSERT(rom_array.state[index][1] != UNINITIALIZED_MEMORY_RECORD); +// const auto value1 = get_variable(rom_array.state[index][0]); +// const auto value2 = get_variable(rom_array.state[index][1]); +// value_witnesses[0] = add_variable(value1); +// value_witnesses[1] = add_variable(value2); +// RomRecord new_record{ +// .index_witness = index_witness, +// .value_column1_witness = value_witnesses[0], +// .value_column2_witness = value_witnesses[1], +// .index = index, +// .record_witness = 0, +// .gate_index = 0, +// }; +// create_ROM_gate(new_record); +// rom_array.records.emplace_back(new_record); + +// // create_read_gate +// return value_witnesses; +// } + +/** + * @brief Compute additional gates required to validate ROM reads. Called when generating the proving key + * + * @param rom_id The id of the ROM table + * @param gate_offset_from_public_inputs Required to track the gate position of where we're adding extra gates + */ +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; + create_tag(read_tag, sorted_list_tag); + create_tag(sorted_list_tag, read_tag); + + // Make sure that every cell has been initialized + for (size_t i = 0; i < rom_array.state.size(); ++i) { + if (rom_array.state[i][0] == UNINITIALIZED_MEMORY_RECORD) { + set_ROM_element_pair(rom_id, static_cast(i), { zero_idx, zero_idx }); + } + } + +#ifdef NO_TBB + std::sort(rom_array.records.begin(), rom_array.records.end()); +#else + std::sort(std::execution::par_unseq, rom_array.records.begin(), rom_array.records.end()); +#endif + + for (const RomRecord& record : rom_array.records) { + const auto index = record.index; + const auto value1 = get_variable(record.value_column1_witness); + const auto value2 = get_variable(record.value_column2_witness); + const auto index_witness = add_variable(fr((uint64_t)index)); + const auto value1_witness = add_variable(value1); + const auto value2_witness = add_variable(value2); + RomRecord sorted_record{ + .index_witness = index_witness, + .value_column1_witness = value1_witness, + .value_column2_witness = value2_witness, + .index = index, + .record_witness = 0, + .gate_index = 0, + }; + create_sorted_ROM_gate(sorted_record); + + assign_tag(record.record_witness, read_tag); + assign_tag(sorted_record.record_witness, sorted_list_tag); + + // For ROM/RAM gates, the 'record' wire value (wire column 4) is a linear combination of the first 3 wire + // values. However...the record value uses the random challenge 'eta', generated after the first 3 wires are + // committed to. i.e. we can't compute the record witness here because we don't know what `eta` is! Take the + // gate indices of the two rom gates (original read gate + sorted gate) and store in `memory_records`. Once + // we + // generate the `eta` challenge, we'll use `memory_records` to figure out which gates need a record wire + // value + // to be computed. + // record (w4) = w3 * eta^3 + w2 * eta^2 + w1 * eta + read_write_flag (0 for reads, 1 for writes) + // Separate containers used to store gate indices of reads and writes. Need to differentiate because of + // `read_write_flag` (N.B. all ROM accesses are considered reads. Writes are for RAM operations) + memory_read_records.push_back(static_cast(sorted_record.gate_index + gate_offset_from_public_inputs)); + memory_read_records.push_back(static_cast(record.gate_index + gate_offset_from_public_inputs)); + } + // One of the checks we run on the sorted list, is to validate the difference between + // the index field across two gates is either 0 or 1. + // If we add a dummy gate at the end of the sorted list, where we force the first wire to + // equal `m + 1`, where `m` is the maximum allowed index in the sorted list, + // 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, + }); + // 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. +} + +/** + * @brief Compute additional gates required to validate RAM read/writes. Called when generating the proving key + * + * @param ram_id The id of the RAM table + * @param gate_offset_from_public_inputs Required to track the gate position of where we're adding extra gates + */ +void UltraCircuitConstructor::process_RAM_array(const size_t ram_id, const size_t gate_offset_from_public_inputs) +{ + RamTranscript& ram_array = ram_arrays[ram_id]; + const auto access_tag = get_new_tag(); // current_tag + 1; + const auto sorted_list_tag = get_new_tag(); // current_tag + 2; + create_tag(access_tag, sorted_list_tag); + create_tag(sorted_list_tag, access_tag); + + // Make sure that every cell has been initialized + // TODO: throw some kind of error here? Circuit should initialize all RAM elements to prevent errors. + // e.g. if a RAM record is uninitialized but the index of that record is a function of public/private inputs, + // different public iputs will produce different circuit constraints. + for (size_t i = 0; i < ram_array.state.size(); ++i) { + if (ram_array.state[i] == UNINITIALIZED_MEMORY_RECORD) { + init_RAM_element(ram_id, static_cast(i), zero_idx); + } + } + +#ifdef NO_TBB + std::sort(ram_array.records.begin(), ram_array.records.end()); +#else + std::sort(std::execution::par_unseq, ram_array.records.begin(), ram_array.records.end()); +#endif + + // Iterate over all but final RAM record. + for (size_t i = 0; i < ram_array.records.size(); ++i) { + const RamRecord& record = ram_array.records[i]; + + const auto index = record.index; + const auto value = get_variable(record.value_witness); + const auto index_witness = add_variable(fr((uint64_t)index)); + const auto timestamp_witess = add_variable(record.timestamp); + const auto value_witness = add_variable(value); + RamRecord sorted_record{ + .index_witness = index_witness, + .timestamp_witness = timestamp_witess, + .value_witness = value_witness, + .index = index, + .timestamp = record.timestamp, + .access_type = record.access_type, + .record_witness = 0, + .gate_index = 0, + }; + + // We don't apply the RAM consistency check gate to the final record, + // as this gate expects a RAM record to be present at the next gate + if (i < ram_array.records.size() - 1) { + create_sorted_RAM_gate(sorted_record); + } else { + // For the final record in the sorted list, we do not apply the full consistency check gate. + // Only need to check the index value = RAM array size - 1. + create_final_sorted_RAM_gate(sorted_record, ram_array.state.size()); + } + + // Assign record/sorted records to tags that we will perform set equivalence checks on + assign_tag(record.record_witness, access_tag); + assign_tag(sorted_record.record_witness, sorted_list_tag); + + // For ROM/RAM gates, the 'record' wire value (wire column 4) is a linear combination of the first 3 wire + // values. However...the record value uses the random challenge 'eta', generated after the first 3 wires are + // committed to. i.e. we can't compute the record witness here because we don't know what `eta` is! Take the + // gate indices of the two rom gates (original read gate + sorted gate) and store in `memory_records`. Once + // we + // generate the `eta` challenge, we'll use `memory_records` to figure out which gates need a record wire + // value + // to be computed. + + switch (record.access_type) { + case RamRecord::AccessType::READ: { + memory_read_records.push_back( + static_cast(sorted_record.gate_index + gate_offset_from_public_inputs)); + memory_read_records.push_back(static_cast(record.gate_index + gate_offset_from_public_inputs)); + break; + } + case RamRecord::AccessType::WRITE: { + memory_write_records.push_back( + static_cast(sorted_record.gate_index + gate_offset_from_public_inputs)); + memory_write_records.push_back(static_cast(record.gate_index + gate_offset_from_public_inputs)); + break; + } + default: { + ASSERT(false); // shouldn't get here! + } + } + } + + // Step 2: Create gates that validate correctness of RAM timestamps + + std::vector timestamp_deltas; + for (size_t i = 0; i < ram_array.records.size() - 1; ++i) { + // create_RAM_timestamp_gate(sorted_records[i], sorted_records[i + 1]) + const auto& current = ram_array.records[i]; + const auto& next = ram_array.records[i + 1]; + + const bool share_index = current.index == next.index; + + fr timestamp_delta = 0; + if (share_index) { + ASSERT(next.timestamp > current.timestamp); + timestamp_delta = fr(next.timestamp - current.timestamp); + } + + uint32_t timestamp_delta_witness = add_variable(timestamp_delta); + + apply_aux_selectors(AUX_SELECTORS::RAM_TIMESTAMP_CHECK); + w_l.emplace_back(current.index_witness); + w_r.emplace_back(current.timestamp_witness); + w_o.emplace_back(timestamp_delta_witness); + w_4.emplace_back(zero_idx); + ++num_gates; + + // store timestamp offsets for later. Need to apply range checks to them, but calling + // `create_new_range_constraint` can add gates. Would ruin the structure of our sorted timestamp list. + timestamp_deltas.push_back(timestamp_delta_witness); + } + + // add the index/timestamp values of the last sorted record in an empty add gate. + // (the previous gate will access the wires on this gate and requires them to be those of the last record) + const auto& last = ram_array.records[ram_array.records.size() - 1]; + create_big_add_gate({ + last.index_witness, + last.timestamp_witness, + zero_idx, + zero_idx, + 0, + 0, + 0, + 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) { + create_new_range_constraint(w, max_timestamp); + } +} + +void UltraCircuitConstructor::process_ROM_arrays(const size_t gate_offset_from_public_inputs) +{ + for (size_t i = 0; i < rom_arrays.size(); ++i) { + process_ROM_array(i, gate_offset_from_public_inputs); + } +} +void UltraCircuitConstructor::process_RAM_arrays(const size_t gate_offset_from_public_inputs) +{ + for (size_t i = 0; i < ram_arrays.size(); ++i) { + process_RAM_array(i, gate_offset_from_public_inputs); + } +} + +} // namespace bonk \ No newline at end of file diff --git a/cpp/src/barretenberg/proof_system/circuit_constructors/ultra_circuit_constructor.hpp b/cpp/src/barretenberg/proof_system/circuit_constructors/ultra_circuit_constructor.hpp new file mode 100644 index 0000000000..fe9d23830f --- /dev/null +++ b/cpp/src/barretenberg/proof_system/circuit_constructors/ultra_circuit_constructor.hpp @@ -0,0 +1,539 @@ +#pragma once +#include "barretenberg/proof_system/arithmetization/arithmetization.hpp" +#include "barretenberg/proof_system/types/polynomial_manifest.hpp" +#include "circuit_constructor_base.hpp" +#include "barretenberg/plonk/proof_system/constants.hpp" +#include "barretenberg/proof_system/flavor/flavor.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/plonk/proof_system/types/prover_settings.hpp" +#include + +namespace bonk { + +static constexpr plonk::ComposerType type = plonk::ComposerType::PLOOKUP; +static constexpr plonk::MerkleHashType merkle_hash_type = plonk::MerkleHashType::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; + +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; +}; + +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 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; +}; + +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; +} + +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); + std::vector& w_4 = std::get<3>(wires); + + std::vector& q_m = std::get<0>(selectors); + std::vector& q_c = std::get<1>(selectors); + std::vector& q_1 = std::get<2>(selectors); + std::vector& q_2 = std::get<3>(selectors); + std::vector& q_3 = std::get<4>(selectors); + std::vector& q_4 = std::get<5>(selectors); + std::vector& q_arith = std::get<6>(selectors); + std::vector& q_sort = std::get<7>(selectors); + std::vector& q_elliptic = std::get<8>(selectors); + std::vector& 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 plonk::ComposerType type = plonk::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 + std::map constant_variable_indices; + + std::vector lookup_tables; + std::vector lookup_multi_tables; + std::map range_lists; // DOCTODO: explain this. + + /** + * @brief Each entry in ram_arrays represents an independent RAM table. + * RamTranscript tracks the current table state, + * as well as the 'records' produced by each read and write operation. + * Used in `compute_proving_key` to generate consistency check gates required to validate the RAM read/write history + */ + std::vector ram_arrays; + + /** + * @brief Each entry in ram_arrays represents an independent ROM table. + * RomTranscript tracks the current table state, + * as well as the 'records' produced by each read operation. + * Used in `compute_proving_key` to generate consistency check gates required to validate the ROM read history + */ + std::vector rom_arrays; + + // Stores gate index of ROM and RAM reads (required by proving key) + std::vector memory_read_records; + // Stores gate index of RAM writes (required by proving key) + std::vector memory_write_records; + + bool circuit_finalised = false; + + UltraCircuitConstructor(const size_t size_hint = 0) + : CircuitConstructorBase(ultra_selector_names(), size_hint) + { + w_l.reserve(size_hint); + w_r.reserve(size_hint); + w_o.reserve(size_hint); + w_4.reserve(size_hint); + zero_idx = put_constant_variable(0); + tau.insert({ DUMMY_TAG, DUMMY_TAG }); + }; + + UltraCircuitConstructor(const UltraCircuitConstructor& other) = delete; + UltraCircuitConstructor(UltraCircuitConstructor&& other) = default; + UltraCircuitConstructor& operator=(const UltraCircuitConstructor& other) = delete; + UltraCircuitConstructor& operator=(UltraCircuitConstructor&& other) = delete; + ~UltraCircuitConstructor() override = default; + + void finalize_circuit(); + + void create_add_gate(const add_triple& in) override; + + void create_big_add_gate(const add_quad& in, const bool use_next_gate_w_4 = false); + void create_big_add_gate_with_bit_extraction(const add_quad& in); + void create_big_mul_gate(const mul_quad& in); + void create_balanced_add_gate(const add_quad& in); + + void create_mul_gate(const mul_triple& in) override; + void create_bool_gate(const uint32_t a) override; + void create_poly_gate(const poly_triple& in) override; + void create_ecc_add_gate(const ecc_add_gate& in); + + void fix_witness(const uint32_t witness_index, const barretenberg::fr& witness_value); + + // void add_recursive_proof(const std::vector& proof_output_witness_indices) + // { + // if (contains_recursive_proof) { + // failure("added recursive proof when one already exists"); + // } + // contains_recursive_proof = true; + + // for (const auto& idx : proof_output_witness_indices) { + // set_public_input(idx); + // recursive_proof_public_input_indices.push_back((uint32_t)(public_inputs.size() - 1)); + // } + // } + + void create_new_range_constraint(const uint32_t variable_index, + const uint64_t target_range, + std::string const msg = "create_new_range_constraint"); + void create_range_constraint(const uint32_t variable_index, const size_t num_bits, std::string const& msg) + { + if (num_bits <= DEFAULT_PLOOKUP_RANGE_BITNUM) { + /** + * N.B. if `variable_index` is not used in any arithmetic constraints, this will create an unsatisfiable + * circuit! + * this range constraint will increase the size of the 'sorted set' of range-constrained integers by 1. + * The 'non-sorted set' of range-constrained integers is a subset of the wire indices of all arithmetic + * gates. No arithemtic gate => size imbalance between sorted and non-sorted sets. Checking for this + * and throwing an error would require a refactor of the Composer to catelog all 'orphan' variables not + * assigned to gates. + **/ + create_new_range_constraint(variable_index, 1ULL << num_bits, msg); + } else { + decompose_into_default_range(variable_index, num_bits, DEFAULT_PLOOKUP_RANGE_BITNUM, msg); + } + } + + accumulator_triple create_logic_constraint(const uint32_t a, + const uint32_t b, + const size_t num_bits, + bool is_xor_gate); + accumulator_triple create_and_constraint(const uint32_t a, const uint32_t b, const size_t num_bits); + accumulator_triple create_xor_constraint(const uint32_t a, const uint32_t b, const size_t num_bits); + + uint32_t put_constant_variable(const barretenberg::fr& variable); + + size_t get_num_constant_gates() const override { return 0; } + + // /** + // * @brief Get the final number of gates in a circuit, which consists of the sum of: + // * 1) Current number number of actual gates + // * 2) Number of public inputs, as we'll need to add a gate for each of them + // * 3) Number of Rom array-associated gates + // * 4) NUmber of range-list associated gates + // * + // * + // * @param count return arument, number of existing gates + // * @param rangecount return argument, extra gates due to range checks + // * @param romcount return argument, extra gates due to rom reads + // * @param ramcount return argument, extra gates due to ram read/writes + // */ + // void get_num_gates_split_into_components(size_t& count, + // size_t& rangecount, + // size_t& romcount, + // size_t& ramcount) const + // { + // count = num_gates; + // // each ROM gate adds +1 extra gate due to the rom reads being copied to a sorted list set + // for (size_t i = 0; i < rom_arrays.size(); ++i) { + // for (size_t j = 0; j < rom_arrays[i].state.size(); ++j) { + // if (rom_arrays[i].state[j][0] == UNINITIALIZED_MEMORY_RECORD) { + // romcount += 2; + // } + // } + // romcount += (rom_arrays[i].records.size()); + // romcount += 1; // we add an addition gate after procesing a rom array + // } + + // constexpr size_t gate_width = ultra_settings::program_width; + // // each RAM gate adds +2 extra gates due to the ram reads being copied to a sorted list set, + // // as well as an extra gate to validate timestamps + // std::vector ram_timestamps; + // std::vector ram_range_sizes; + // std::vector ram_range_exists; + // for (size_t i = 0; i < ram_arrays.size(); ++i) { + // for (size_t j = 0; j < ram_arrays[i].state.size(); ++j) { + // if (ram_arrays[i].state[j] == UNINITIALIZED_MEMORY_RECORD) { + // ramcount += NUMBER_OF_GATES_PER_RAM_ACCESS; + // } + // } + // ramcount += (ram_arrays[i].records.size() * NUMBER_OF_GATES_PER_RAM_ACCESS); + // ramcount += NUMBER_OF_ARITHMETIC_GATES_PER_RAM_ARRAY; // we add an addition gate after procesing a ram + // array + + // // there will be 'max_timestamp' number of range checks, need to calculate. + // const auto max_timestamp = ram_arrays[i].access_count - 1; + + // // if a range check of length `max_timestamp` already exists, we are double counting. + // // We record `ram_timestamps` to detect and correct for this error when we process range lists. + // ram_timestamps.push_back(max_timestamp); + // size_t padding = (gate_width - (max_timestamp % gate_width)) % gate_width; + // if (max_timestamp == gate_width) + // padding += gate_width; + // const size_t ram_range_check_list_size = max_timestamp + padding; + + // size_t ram_range_check_gate_count = (ram_range_check_list_size / gate_width); + // ram_range_check_gate_count += 1; // we need to add 1 extra addition gates for every distinct range list + + // ram_range_sizes.push_back(ram_range_check_gate_count); + // ram_range_exists.push_back(false); + // // rangecount += ram_range_check_gate_count; + // } + // for (const auto& list : range_lists) { + // auto list_size = list.second.variable_indices.size(); + // size_t padding = (gate_width - (list.second.variable_indices.size() % gate_width)) % gate_width; + // if (list.second.variable_indices.size() == gate_width) + // padding += gate_width; + // list_size += padding; + + // for (size_t i = 0; i < ram_timestamps.size(); ++i) { + // if (list.second.target_range == ram_timestamps[i]) { + // ram_range_exists[i] = true; + // } + // } + // rangecount += (list_size / gate_width); + // rangecount += 1; // we need to add 1 extra addition gates for every distinct range list + // } + // // update rangecount to include the ram range checks the composer will eventually be creating + // for (size_t i = 0; i < ram_range_sizes.size(); ++i) { + // if (!ram_range_exists[i]) { + // rangecount += ram_range_sizes[i]; + // } + // } + // } + + // /** + // * @brief Get the final number of gates in a circuit, which consists of the sum of: + // * 1) Current number number of actual gates + // * 2) Number of public inputs, as we'll need to add a gate for each of them + // * 3) Number of Rom array-associated gates + // * 4) NUmber of range-list associated gates + // * + // * @return size_t + // */ + // virtual size_t get_num_gates() const override + // { + // // if circuit finalised already added extra gates + // if (circuit_finalised) { + // return num_gates; + // } + // size_t count = 0; + // size_t rangecount = 0; + // size_t romcount = 0; + // size_t ramcount = 0; + // get_num_gates_split_into_components(count, rangecount, romcount, ramcount); + // return count + romcount + ramcount + rangecount; + // } + + // virtual void print_num_gates() const override + // { + // size_t count = 0; + // size_t rangecount = 0; + // size_t romcount = 0; + // size_t ramcount = 0; + + // get_num_gates_split_into_components(count, rangecount, romcount, ramcount); + + // size_t total = count + romcount + ramcount + rangecount; + // std::cout << "gates = " << total << " (arith " << count << ", rom " << romcount << ", ram " << ramcount + // << ", range " << rangecount << "), pubinp = " << public_inputs.size() << std::endl; + // } + + void assert_equal_constant(const uint32_t a_idx, + const barretenberg::fr& b, + std::string const& msg = "assert equal constant") + { + if (variables[a_idx] != b && !failed()) { + failure(msg); + } + auto b_idx = put_constant_variable(b); + assert_equal(a_idx, b_idx, msg); + } + + /** + * Plookup Methods + **/ + void add_table_column_selector_poly_to_proving_key(barretenberg::polynomial& small, const std::string& tag); + void initialize_precomputed_table( + const plookup::BasicTableId id, + bool (*generator)(std::vector&, + std::vector&, + std::vector&), + std::array (*get_values_from_key)(const std::array)); + + plookup::BasicTable& get_table(const plookup::BasicTableId id); + plookup::MultiTable& create_table(const plookup::MultiTableId id); + + plookup::ReadData create_gates_from_plookup_accumulators( + const plookup::MultiTableId& id, + const plookup::ReadData& read_values, + const uint32_t key_a_index, + std::optional key_b_index = std::nullopt); + + /** + * Generalized Permutation Methods + **/ + 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, + std::string const& msg = "decompose_into_default_range"); + std::vector decompose_into_default_range_better_for_oddlimbnum( + const uint32_t variable_index, + const size_t num_bits, + std::string const& msg = "decompose_into_default_range_better_for_oddlimbnum"); + void create_dummy_constraints(const std::vector& variable_index); + void create_sort_constraint(const std::vector& variable_index); + void create_sort_constraint_with_edges(const std::vector& variable_index, + const barretenberg::fr&, + const barretenberg::fr&); + void assign_tag(const uint32_t variable_index, const uint32_t tag) + { + ASSERT(tag <= current_tag); + ASSERT(real_variable_tags[real_variable_index[variable_index]] == DUMMY_TAG); + real_variable_tags[real_variable_index[variable_index]] = tag; + } + + uint32_t create_tag(const uint32_t tag_index, const uint32_t tau_index) + { + tau.insert({ tag_index, tau_index }); + current_tag++; // Why exactly? + return current_tag; + } + + uint32_t get_new_tag() + { + current_tag++; + return current_tag; + } + + RangeList create_range_list(const uint64_t target_range); + void process_range_list(const RangeList& list); + void process_range_lists(); + + /** + * Custom Gate Selectors + **/ + void apply_aux_selectors(const AUX_SELECTORS type); + + /** + * 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); + 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 evaluate_non_native_field_multiplication( + const non_native_field_witnesses& input, const bool range_constrain_quotient_and_remainder = true); + std::array evaluate_partial_non_native_field_multiplication(const non_native_field_witnesses& input); + typedef std::pair scaled_witness; + typedef std::tuple add_simple; + std::array evaluate_non_native_field_subtraction( + add_simple limb0, + add_simple limb1, + add_simple limb2, + add_simple limb3, + std::tuple limbp); + std::array evaluate_non_native_field_addition(add_simple limb0, + add_simple limb1, + add_simple limb2, + add_simple limb3, + std::tuple limbp); + + /** + * Memory + **/ + + // size_t create_RAM_array(const size_t array_size); + size_t create_ROM_array(const size_t array_size); + + void set_ROM_element(const size_t rom_id, const size_t index_value, const uint32_t value_witness); + void set_ROM_element_pair(const size_t rom_id, + const size_t index_value, + const std::array& value_witnesses); + uint32_t read_ROM_array(const size_t rom_id, const uint32_t index_witness); + std::array read_ROM_array_pair(const size_t rom_id, const uint32_t index_witness); + void create_ROM_gate(RomRecord& record); + void create_sorted_ROM_gate(RomRecord& record); + void process_ROM_array(const size_t rom_id, const size_t gate_offset_from_public_inputs); + void process_ROM_arrays(const size_t gate_offset_from_public_inputs); + + void create_RAM_gate(RamRecord& record); + void create_sorted_RAM_gate(RamRecord& record); + void create_final_sorted_RAM_gate(RamRecord& record, const size_t ram_array_size); + + size_t create_RAM_array(const size_t array_size); + void init_RAM_element(const size_t ram_id, const size_t index_value, const uint32_t value_witness); + uint32_t read_RAM_array(const size_t ram_id, const uint32_t index_witness); + void write_RAM_array(const size_t ram_id, const uint32_t index_witness, const uint32_t value_witness); + void process_RAM_array(const size_t ram_id, const size_t gate_offset_from_public_inputs); + void process_RAM_arrays(const size_t gate_offset_from_public_inputs); +}; +} // namespace bonk diff --git a/cpp/src/barretenberg/proof_system/composer/composer_helper_lib.cpp b/cpp/src/barretenberg/proof_system/composer/composer_helper_lib.cpp index 25742b9061..0ea9320ccd 100644 --- a/cpp/src/barretenberg/proof_system/composer/composer_helper_lib.cpp +++ b/cpp/src/barretenberg/proof_system/composer/composer_helper_lib.cpp @@ -8,6 +8,7 @@ #include "barretenberg/honk/pcs/commitment_key.hpp" #include "barretenberg/proof_system/circuit_constructors/standard_circuit_constructor.hpp" #include "barretenberg/proof_system/circuit_constructors/turbo_circuit_constructor.hpp" +#include "barretenberg/proof_system/circuit_constructors/ultra_circuit_constructor.hpp" namespace bonk { /** @@ -98,9 +99,6 @@ void enforce_nonzero_polynomial_selectors(const CircuitConstructor& circuit_cons * @brief Retrieve lagrange forms of selector polynomials and compute monomial and coset-monomial forms and put into * cache * - * @note This function also deletes the lagrange forms of the selectors from memory since they are not needed - * for proof construction once the monomial and coset forms have been computed - * * @param key Pointer to the proving key * @param selector_properties Names of selectors */ @@ -109,7 +107,6 @@ void compute_monomial_and_coset_selector_forms(bonk::proving_key* circuit_provin { for (size_t i = 0; i < selector_properties.size(); i++) { // Compute monomial form of selector polynomial - auto& selector_poly_lagrange = circuit_proving_key->polynomial_store.get(selector_properties[i].name + "_lagrange"); barretenberg::polynomial selector_poly(circuit_proving_key->circuit_size); @@ -120,8 +117,8 @@ void compute_monomial_and_coset_selector_forms(bonk::proving_key* circuit_provin barretenberg::polynomial selector_poly_fft(selector_poly, circuit_proving_key->circuit_size * 4 + 4); selector_poly_fft.coset_fft(circuit_proving_key->large_domain); - // Remove the selector lagrange forms since they will not be needed beyond this point - circuit_proving_key->polynomial_store.remove(selector_properties[i].name + "_lagrange"); + // TODO(luke): For Standard/Turbo, the lagrange polynomials can be removed from the store at this point but this + // is not the case for Ultra. Implement? circuit_proving_key->polynomial_store.put(selector_properties[i].name, std::move(selector_poly)); circuit_proving_key->polynomial_store.put(selector_properties[i].name + "_fft", std::move(selector_poly_fft)); } @@ -238,5 +235,6 @@ std::shared_ptr compute_verification_key_common( COMPILE_FOR_CIRCUIT_CONSTRUCTOR(StandardCircuitConstructor) COMPILE_FOR_CIRCUIT_CONSTRUCTOR(TurboCircuitConstructor) +COMPILE_FOR_CIRCUIT_CONSTRUCTOR(UltraCircuitConstructor) } // namespace bonk diff --git a/cpp/src/barretenberg/proof_system/composer/permutation_helper.hpp b/cpp/src/barretenberg/proof_system/composer/permutation_helper.hpp index 9a1288c816..d9baedaef4 100644 --- a/cpp/src/barretenberg/proof_system/composer/permutation_helper.hpp +++ b/cpp/src/barretenberg/proof_system/composer/permutation_helper.hpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -49,6 +50,7 @@ struct permutation_subgroup_element { using CyclicPermutation = std::vector; namespace { + /** * Compute all CyclicPermutations of the circuit. Each CyclicPermutation represents the indices of the values in the * witness wires that must have the same value. @@ -101,8 +103,8 @@ std::vector compute_wire_copy_cycles(const CircuitConstructor } // Iterate over all variables of the "real" gates, and add a corresponding node to the cycle for that variable - for (size_t j = 0; j < program_width; ++j) { - for (size_t i = 0; i < num_gates; ++i) { + for (size_t i = 0; i < num_gates; ++i) { + for (size_t j = 0; j < program_width; ++j) { // We are looking at the j-th wire in the i-th row. // The value in this position should be equal to the value of the element at index `var_index` // of the `constructor.variables` vector. @@ -304,30 +306,29 @@ inline void compute_standard_plonk_lagrange_polynomial(barretenberg::polynomial& } /** - * @brief Given a permutation mapping, compute sigma permutations polynomials in lagrange form and put them in the - * polynomial cache - * - * @details Iterate over the mapping of each wire and calla function, translating this mapping into lagrange - * evaluations. + * @brief Compute lagrange polynomial from mapping (used for sigmas or ids) * - * @tparam program_width The number of wires - * @param sigma_mappings At table with permutation information - * @param key Pointer to the proving key + * @tparam program_width + * @param mappings + * @param label + * @param key */ template -void compute_standard_plonk_sigma_lagrange_polynomials_from_mapping( - std::array, program_width>& sigma_mappings, bonk::proving_key* key) +void compute_plonk_permutation_lagrange_polynomials_from_mapping( + std::string label, + std::array, program_width>& mappings, + bonk::proving_key* key) { for (size_t i = 0; i < program_width; i++) { std::string index = std::to_string(i + 1); - barretenberg::polynomial sigma_polynomial_lagrange(key->circuit_size); - compute_standard_plonk_lagrange_polynomial(sigma_polynomial_lagrange, sigma_mappings[i], key->small_domain); - key->polynomial_store.put("sigma_" + index + "_lagrange", std::move(sigma_polynomial_lagrange)); + barretenberg::polynomial polynomial_lagrange(key->circuit_size); + compute_standard_plonk_lagrange_polynomial(polynomial_lagrange, mappings[i], key->small_domain); + key->polynomial_store.put(label + "_" + index + "_lagrange", std::move(polynomial_lagrange)); } } /** - * @brief Compute the monomial and coset version of each sigma polynomial + * @brief Compute the monomial and coset-fft version of each lagrange polynomial of the given label * * @details For Plonk we need the monomial and coset form of the polynomials, so we retrieve the lagrange form from * polynomial cache, compute FFT versions and put them in the cache @@ -335,14 +336,15 @@ void compute_standard_plonk_sigma_lagrange_polynomials_from_mapping( * @tparam program_width Number of wires * @param key Pointer to the proving key */ -template void compute_sigma_polynomials_monomial_and_coset_fft(bonk::proving_key* key) +template +void compute_monomial_and_coset_fft_polynomials_from_lagrange(std::string label, bonk::proving_key* key) { for (size_t i = 0; i < program_width; ++i) { - - // Construct permutation polynomials in lagrange base std::string index = std::to_string(i + 1); + std::string prefix = label + "_" + index; - barretenberg::polynomial sigma_polynomial_lagrange = key->polynomial_store.get("sigma_" + index + "_lagrange"); + // Construct permutation polynomials in lagrange base + barretenberg::polynomial sigma_polynomial_lagrange = key->polynomial_store.get(prefix + "_lagrange"); // Compute permutation polynomial monomial form barretenberg::polynomial sigma_polynomial(key->circuit_size); barretenberg::polynomial_arithmetic::ifft( @@ -352,8 +354,8 @@ template void compute_sigma_polynomials_monomial_and_cose barretenberg::polynomial sigma_fft(sigma_polynomial, key->large_domain.size); sigma_fft.coset_fft(key->large_domain); - key->polynomial_store.put("sigma_" + index, std::move(sigma_polynomial)); - key->polynomial_store.put("sigma_" + index + "_fft", std::move(sigma_fft)); + key->polynomial_store.put(prefix, std::move(sigma_polynomial)); + key->polynomial_store.put(prefix + "_fft", std::move(sigma_fft)); } } @@ -420,9 +422,9 @@ void compute_standard_plonk_sigma_permutations(CircuitConstructor& circuit_const // Compute the permutation table specifying which element becomes which auto sigma_mappings = compute_basic_bonk_sigma_permutations(circuit_constructor, key); // Compute Plonk-style sigma polynomials from the mapping - compute_standard_plonk_sigma_lagrange_polynomials_from_mapping(sigma_mappings, key); + compute_plonk_permutation_lagrange_polynomials_from_mapping("sigma", sigma_mappings, key); // Compute their monomial and coset versions - compute_sigma_polynomials_monomial_and_coset_fft(key); + compute_monomial_and_coset_fft_polynomials_from_lagrange("sigma", key); } /** @@ -433,7 +435,6 @@ void compute_standard_plonk_sigma_permutations(CircuitConstructor& circuit_const inline void compute_first_and_last_lagrange_polynomials(auto key) // proving_key* and share_ptr { const size_t n = key->circuit_size; - // info("Computing Lagrange basis polys, the value of n is: ",/s n); barretenberg::polynomial lagrange_polynomial_0(n); barretenberg::polynomial lagrange_polynomial_n_min_1(n); lagrange_polynomial_0[0] = 1; @@ -442,4 +443,97 @@ inline void compute_first_and_last_lagrange_polynomials(auto key) // proving_key key->polynomial_store.put("L_last_lagrange", std::move(lagrange_polynomial_n_min_1)); } +/** + * @brief Compute generalized permutation sigmas and ids for ultra plonk + * + * @tparam program_width + * @tparam CircuitConstructor + * @param circuit_constructor + * @param key + * @return std::array, program_width> + */ +template +void compute_plonk_generalized_sigma_permutations(const CircuitConstructor& circuit_constructor, bonk::proving_key* key) +{ + // Compute wire copy cycles for public and private variables + auto wire_copy_cycles = compute_wire_copy_cycles(circuit_constructor); + std::array, program_width> sigma_mappings; + std::array, program_width> id_mappings; + + // Instantiate the sigma and id mappings by reserving enough space and pushing 'default' permutation subgroup + // elements that point to themselves. + for (size_t i = 0; i < program_width; ++i) { + sigma_mappings[i].reserve(key->circuit_size); + id_mappings[i].reserve(key->circuit_size); + } + for (size_t i = 0; i < program_width; ++i) { + for (size_t j = 0; j < key->circuit_size; ++j) { + sigma_mappings[i].emplace_back(permutation_subgroup_element{ + .row_index = (uint32_t)j, .column_index = (uint8_t)i, .is_public_input = false, .is_tag = false }); + + id_mappings[i].emplace_back(permutation_subgroup_element{ + .row_index = (uint32_t)j, .column_index = (uint8_t)i, .is_public_input = false, .is_tag = false }); + } + } + + // // Represents the index of a variable in circuit_constructor.variables + std::span real_variable_tags = circuit_constructor.real_variable_tags; + // const std::map& tau = circuit_constructor.tau; + + // Go through all wire cycles and update sigma and id mappings to point to the next element + // within each cycle as well as set the appropriate tags + for (size_t i = 0; i < wire_copy_cycles.size(); ++i) { + for (size_t j = 0; j < wire_copy_cycles[i].size(); ++j) { + cycle_node current_cycle_node = wire_copy_cycles[i][j]; + size_t next_cycle_node_index = j == wire_copy_cycles[i].size() - 1 ? 0 : j + 1; + cycle_node next_cycle_node = wire_copy_cycles[i][next_cycle_node_index]; + const auto current_row = current_cycle_node.gate_index; + const auto next_row = next_cycle_node.gate_index; + + const uint32_t current_column = current_cycle_node.wire_index; + const uint32_t next_column = next_cycle_node.wire_index; + + sigma_mappings[current_column][current_row] = { + .row_index = next_row, .column_index = (uint8_t)next_column, .is_public_input = false, .is_tag = false + }; + + bool first_node, last_node; + + first_node = j == 0; + last_node = next_cycle_node_index == 0; + if (first_node) { + id_mappings[current_column][current_row].is_tag = true; + id_mappings[current_column][current_row].row_index = (real_variable_tags[i]); + } + if (last_node) { + sigma_mappings[current_column][current_row].is_tag = true; + + // TODO: yikes, std::maps are expensive. Can we find a way to get rid of this? + sigma_mappings[current_column][current_row].row_index = + circuit_constructor.tau.at(real_variable_tags[i]); + } + } + } + + const uint32_t num_public_inputs = static_cast(circuit_constructor.public_inputs.size()); + + // This corresponds in the paper to modifying sigma to sigma' with the zeta_i values; this enforces public input + // consistency + for (size_t i = 0; i < num_public_inputs; ++i) { + sigma_mappings[0][i].row_index = static_cast(i); + sigma_mappings[0][i].column_index = 0; + sigma_mappings[0][i].is_public_input = true; + if (sigma_mappings[0][i].is_tag) { + std::cerr << "MAPPING IS BOTH A TAG AND A PUBLIC INPUT" << std::endl; + } + } + + // Compute Plonk-style sigma and ID polynomials from the corresponding mappings + compute_plonk_permutation_lagrange_polynomials_from_mapping("sigma", sigma_mappings, key); + compute_plonk_permutation_lagrange_polynomials_from_mapping("id", id_mappings, key); + // Compute the monomial and coset-ffts for sigmas and IDs + compute_monomial_and_coset_fft_polynomials_from_lagrange("sigma", key); + compute_monomial_and_coset_fft_polynomials_from_lagrange("id", key); +} + } // namespace bonk diff --git a/cpp/src/barretenberg/proof_system/polynomial_store/polynomial_store.hpp b/cpp/src/barretenberg/proof_system/polynomial_store/polynomial_store.hpp index d7d5688ceb..01b1e7c777 100644 --- a/cpp/src/barretenberg/proof_system/polynomial_store/polynomial_store.hpp +++ b/cpp/src/barretenberg/proof_system/polynomial_store/polynomial_store.hpp @@ -84,6 +84,10 @@ template class PolynomialStore { info(); } + // Basic map methods + bool contains(std::string const& key) { return polynomial_map.contains(key); }; + size_t size() { return polynomial_map.size(); }; + // Allow for const range based for loop typename std::unordered_map::const_iterator begin() const {