From 5979fd44b6c16135e736eda343a2a4b19be9d619 Mon Sep 17 00:00:00 2001 From: ledwards2225 <98505400+ledwards2225@users.noreply.github.com> Date: Wed, 12 Apr 2023 06:23:35 -0700 Subject: [PATCH] UltraHonk Composer (Split) (https://github.com/AztecProtocol/barretenberg/pull/339) * add split UltraHonk composer and checks for consistency with UltraPlonk * adding issue number to some TODOs --- .../standard_honk_composer_helper.cpp | 1 + .../ultra_honk_composer_helper.cpp | 352 ++++++ .../ultra_honk_composer_helper.hpp | 72 ++ .../honk/composer/ultra_honk_composer.hpp | 470 ++++++++ .../composer/ultra_honk_composer.test.cpp | 1015 +++++++++++++++++ .../honk/proof_system/prover_library.test.cpp | 21 +- .../honk/proof_system/ultra_prover.cpp | 56 + .../honk/proof_system/ultra_prover.hpp | 63 + .../composer/permutation_helper.hpp | 21 + 9 files changed, 2060 insertions(+), 11 deletions(-) create mode 100644 circuits/cpp/barretenberg/cpp/src/barretenberg/honk/composer/composer_helper/ultra_honk_composer_helper.cpp create mode 100644 circuits/cpp/barretenberg/cpp/src/barretenberg/honk/composer/composer_helper/ultra_honk_composer_helper.hpp create mode 100644 circuits/cpp/barretenberg/cpp/src/barretenberg/honk/composer/ultra_honk_composer.hpp create mode 100644 circuits/cpp/barretenberg/cpp/src/barretenberg/honk/composer/ultra_honk_composer.test.cpp create mode 100644 circuits/cpp/barretenberg/cpp/src/barretenberg/honk/proof_system/ultra_prover.cpp create mode 100644 circuits/cpp/barretenberg/cpp/src/barretenberg/honk/proof_system/ultra_prover.hpp diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/composer/composer_helper/standard_honk_composer_helper.cpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/composer/composer_helper/standard_honk_composer_helper.cpp index 8c61e02a5db..6867d9af37a 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/composer/composer_helper/standard_honk_composer_helper.cpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/composer/composer_helper/standard_honk_composer_helper.cpp @@ -165,6 +165,7 @@ StandardProver StandardHonkComposerHelper::create_prover( compute_witness(circuit_constructor); size_t num_sumcheck_rounds(circuit_proving_key->log_circuit_size); + // TODO(luke): what is this manifest? Remove? auto manifest = Flavor::create_manifest(circuit_constructor.public_inputs.size(), num_sumcheck_rounds); StandardProver output_state(std::move(wire_polynomials), circuit_proving_key); diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/composer/composer_helper/ultra_honk_composer_helper.cpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/composer/composer_helper/ultra_honk_composer_helper.cpp new file mode 100644 index 00000000000..26b7781bc41 --- /dev/null +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/composer/composer_helper/ultra_honk_composer_helper.cpp @@ -0,0 +1,352 @@ +#include "ultra_honk_composer_helper.hpp" +#include "barretenberg/honk/proof_system/ultra_prover.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 +#include + +namespace proof_system::honk { + +/** + * @brief Compute witness polynomials + * + * TODO(luke): The wire polynomials are returned directly whereas the sorted list polys are added to the proving + * key. This should be made consistent once Cody's Flavor work is settled. + */ +template +void UltraHonkComposerHelper::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(#340)(luke): within compute_witness_base, the 3rd argument is used in the calculation of the dyadic circuit + // size (subgroup_size). Here (and in other split composers) we're passing in NUM_RANDOMIZED_GATES, but elsewhere, + // e.g. directly above, we use NUM_RESERVED_GATES in a similar role. Therefore, these two constants must be equal + // for everything to be consistent. What we should do is compute the dyadic circuit size once and for all then pass + // that around rather than computing in multiple places. + wire_polynomials = compute_witness_base(circuit_constructor, total_num_gates, NUM_RANDOMIZED_GATES); + + 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; + } + + // TODO(luke): Adding these to the key for now but this is inconsistent since these are 'witness' polys. Need + // to see what becomes of the proving key before making a decision here. + 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 UltraHonkComposerHelper::create_prover(CircuitConstructor& circuit_constructor) +{ + finalize_circuit(circuit_constructor); + + compute_proving_key(circuit_constructor); + compute_witness(circuit_constructor); + + UltraProver output_state(std::move(wire_polynomials), circuit_proving_key); + + 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 UltraHonkComposerHelper::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 UltraHonkComposerHelper::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, ComposerType::PLOOKUP); + + construct_lagrange_selector_forms(circuit_constructor, circuit_proving_key.get()); + + enforce_nonzero_polynomial_selectors(circuit_constructor, circuit_proving_key.get()); + + compute_honk_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: + size_t num_selectors = circuit_constructor.num_selectors; + ASSERT(offset == subgroup_size - 1); + auto unique_last_value = 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; + + circuit_proving_key->polynomial_store.put("table_value_1_lagrange", std::move(poly_q_table_column_1)); + circuit_proving_key->polynomial_store.put("table_value_2_lagrange", std::move(poly_q_table_column_2)); + circuit_proving_key->polynomial_store.put("table_value_3_lagrange", std::move(poly_q_table_column_3)); + circuit_proving_key->polynomial_store.put("table_value_4_lagrange", std::move(poly_q_table_column_4)); + + // 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 UltraHonkComposerHelper::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 UltraHonkComposerHelper::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 UltraHonkComposerHelper; +} // namespace proof_system::honk diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/composer/composer_helper/ultra_honk_composer_helper.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/composer/composer_helper/ultra_honk_composer_helper.hpp new file mode 100644 index 00000000000..de309735e61 --- /dev/null +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/composer/composer_helper/ultra_honk_composer_helper.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include "barretenberg/proof_system/composer/composer_helper_lib.hpp" +#include "barretenberg/plonk/composer/splitting_tmp/composer_helper/composer_helper_lib.hpp" +#include "barretenberg/srs/reference_string/file_reference_string.hpp" +#include "barretenberg/plonk/proof_system/proving_key/proving_key.hpp" +#include "barretenberg/honk/proof_system/ultra_prover.hpp" +// #include "barretenberg/plonk/proof_system/verifier/verifier.hpp" + +#include +#include +#include +#include + +namespace proof_system::honk { +// TODO(Kesha): change initializations to specify this parameter +// Cody: What does this mean? +template class UltraHonkComposerHelper { + public: + // TODO(#340)(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::vector wire_polynomials; + 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 UltraHonkComposerHelper(std::shared_ptr crs_factory) + : crs_factory_(std::move(crs_factory)) + {} + + UltraHonkComposerHelper(std::shared_ptr p_key, std::shared_ptr v_key) + : circuit_proving_key(std::move(p_key)) + , circuit_verification_key(std::move(v_key)) + {} + + UltraHonkComposerHelper(UltraHonkComposerHelper&& other) noexcept = default; + UltraHonkComposerHelper(UltraHonkComposerHelper const& other) noexcept = default; + UltraHonkComposerHelper& operator=(UltraHonkComposerHelper&& other) noexcept = default; + UltraHonkComposerHelper& operator=(UltraHonkComposerHelper const& other) noexcept = default; + ~UltraHonkComposerHelper() = default; + + 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); +}; + +} // namespace proof_system::honk diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/composer/ultra_honk_composer.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/composer/ultra_honk_composer.hpp new file mode 100644 index 00000000000..5fa40b46269 --- /dev/null +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/composer/ultra_honk_composer.hpp @@ -0,0 +1,470 @@ +#pragma once +#include "barretenberg/plonk/composer/plookup_tables/plookup_tables.hpp" +#include "barretenberg/honk/proof_system/ultra_prover.hpp" +#include "barretenberg/proof_system/circuit_constructors/ultra_circuit_constructor.hpp" +#include "barretenberg/honk/composer/composer_helper/ultra_honk_composer_helper.hpp" +#include + +namespace proof_system::honk { + +class UltraHonkComposer { + + 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 + UltraHonkComposerHelper composer_helper; + size_t& num_gates; + + UltraHonkComposer() + : UltraHonkComposer("../srs_db/ignition", 0){}; + + UltraHonkComposer(std::string const& crs_path, const size_t size_hint) + : UltraHonkComposer(std::unique_ptr(new FileReferenceStringFactory(crs_path)), + size_hint){}; + + UltraHonkComposer(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){}; + + UltraHonkComposer(std::shared_ptr const& p_key, + std::shared_ptr const& v_key, + size_t size_hint = 0); + UltraHonkComposer(UltraHonkComposer&& other) = default; + UltraHonkComposer& operator=(UltraHonkComposer&& other) = delete; + ~UltraHonkComposer() = 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); }; + + 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 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 proof_system::honk diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/composer/ultra_honk_composer.test.cpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/composer/ultra_honk_composer.test.cpp new file mode 100644 index 00000000000..29f7b26c554 --- /dev/null +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/composer/ultra_honk_composer.test.cpp @@ -0,0 +1,1015 @@ +#include "ultra_honk_composer.hpp" +#include "barretenberg/common/log.hpp" +#include "barretenberg/honk/proof_system/ultra_prover.hpp" +#include "barretenberg/honk/sumcheck/relations/relation.hpp" +#include "barretenberg/numeric/uint256/uint256.hpp" +#include "barretenberg/honk/flavor/flavor.hpp" +#include +#include +#include "barretenberg/honk/proof_system/prover.hpp" +#include "barretenberg/honk/sumcheck/sumcheck_round.hpp" +#include "barretenberg/honk/sumcheck/relations/grand_product_computation_relation.hpp" +#include "barretenberg/honk/sumcheck/relations/grand_product_initialization_relation.hpp" +#include "barretenberg/honk/utils/public_inputs.hpp" + +// TODO(luke): TEMPORARY; for testing only (comparison with Ultra Plonk composers) +#include "barretenberg/plonk/composer/ultra_composer.hpp" +#include "barretenberg/plonk/composer/splitting_tmp/ultra_plonk_composer.hpp" +#include "barretenberg/plonk/proof_system/prover/prover.hpp" + +#include +#include + +using namespace proof_system::honk; + +namespace test_ultra_honk_composer { + +std::vector add_variables(auto& 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; +} + +/** + * @brief TEMPORARY method for checking consistency of polynomials computed by Ultra Plonk/Honk composers + * + * @param honk_prover + * @param plonk_prover + */ +void verify_consistency(honk::UltraProver& honk_prover, plonk::UltraProver& plonk_prover) +{ + // Check that all lagrange polys agree + auto& honk_store = honk_prover.key->polynomial_store; + auto& plonk_store = plonk_prover.key->polynomial_store; + for (auto& entry : honk_store) { + std::string key = entry.first; + if (plonk_store.contains(key)) { + ASSERT_EQ(honk_store.get(key), plonk_store.get(key)); + } + } + + // Check that all wires agree + for (size_t i = 0; i < 4; ++i) { + std::string label = "w_" + std::to_string(i + 1) + "_lagrange"; + ASSERT_EQ(honk_prover.wire_polynomials[i], plonk_prover.key->polynomial_store.get(label)); + } +} + +/** + * @brief TEMPORARY (verbose) method for checking consistency of polynomials computed by Ultra Plonk/Honk composers + * + * @param honk_prover + * @param plonk_prover + */ +void check_consistency(honk::UltraProver& honk_prover, plonk::UltraProver& plonk_prover) +{ + auto& honk_store = honk_prover.key->polynomial_store; + auto& plonk_store = plonk_prover.key->polynomial_store; + for (auto& entry : honk_store) { + std::string key = entry.first; + if (plonk_store.contains(key)) { + + bool polys_equal = (honk_store.get(key) == plonk_store.get(key)); + if (polys_equal) { + info("Equal: ", key); + } + if (!polys_equal) { + info("UNEQUAL: ", key); + } + } + } + + for (size_t i = 0; i < 4; ++i) { + std::string label = "w_" + std::to_string(i + 1) + "_lagrange"; + bool wire_equal = (honk_prover.wire_polynomials[i] == plonk_prover.key->polynomial_store.get(label)); + if (wire_equal) { + info("Wire Equal: ", i); + } + if (!wire_equal) { + info("Wire UNEQUAL: ", i); + } + } + + // std::string label = "w_1_lagrange"; + // for (size_t i = 0; i < plonk_store.get(label).size(); ++i) { + // auto val_honk = honk_prover.wire_polynomials[0][i]; + // // auto val_honk = honk_store.get(label)[i]; + // auto val_plonk = plonk_store.get(label)[i]; + // if (val_honk != val_plonk) { + // info("UNEQUAL index = ", i); + // info("honk: ",val_honk); + // info("plonk: ", val_plonk); + // } + // } +} + +TEST(UltraHonkComposer, create_gates_from_plookup_accumulators) +{ + auto honk_composer = UltraHonkComposer(); + auto plonk_composer = proof_system::plonk::UltraComposer(); + + 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 = honk_composer.add_variable(input_hi); + const auto input_lo_index = honk_composer.add_variable(input_lo); + + const auto sequence_data_hi = + plookup::get_lookup_accumulators(plookup::MultiTableId::PEDERSEN_LEFT_HI, input_hi); + const auto sequence_data_lo = + plookup::get_lookup_accumulators(plookup::MultiTableId::PEDERSEN_LEFT_LO, input_lo); + + const auto lookup_witnesses_hi = honk_composer.create_gates_from_plookup_accumulators( + plookup::MultiTableId::PEDERSEN_LEFT_HI, sequence_data_hi, input_hi_index); + const auto lookup_witnesses_lo = honk_composer.create_gates_from_plookup_accumulators( + plookup::MultiTableId::PEDERSEN_LEFT_LO, sequence_data_lo, input_lo_index); + } + { + 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 = plonk_composer.add_variable(input_hi); + const auto input_lo_index = plonk_composer.add_variable(input_lo); + + const auto sequence_data_hi = + plookup::get_lookup_accumulators(plookup::MultiTableId::PEDERSEN_LEFT_HI, input_hi); + const auto sequence_data_lo = + plookup::get_lookup_accumulators(plookup::MultiTableId::PEDERSEN_LEFT_LO, input_lo); + + const auto lookup_witnesses_hi = plonk_composer.create_gates_from_plookup_accumulators( + plookup::MultiTableId::PEDERSEN_LEFT_HI, sequence_data_hi, input_hi_index); + const auto lookup_witnesses_lo = plonk_composer.create_gates_from_plookup_accumulators( + plookup::MultiTableId::PEDERSEN_LEFT_LO, sequence_data_lo, input_lo_index); + } + + auto honk_prover = honk_composer.create_prover(); + auto plonk_prover = plonk_composer.create_prover(); + + verify_consistency(honk_prover, plonk_prover); +} + +/** + * @brief Build UltraHonkComposer + * + */ +TEST(UltraHonkComposer, test_no_lookup_proof) +{ + auto honk_composer = UltraHonkComposer(); + auto plonk_composer = proof_system::plonk::UltraComposer(); + + size_t MM = 4; + for (size_t i = 0; i < MM; ++i) { + for (size_t j = 0; j < MM; ++j) { + uint64_t left = static_cast(j); + uint64_t right = static_cast(i); + uint32_t left_idx = honk_composer.add_variable(fr(left)); + uint32_t right_idx = honk_composer.add_variable(fr(right)); + uint32_t result_idx = honk_composer.add_variable(fr(left ^ right)); + + uint32_t add_idx = + honk_composer.add_variable(fr(left) + fr(right) + honk_composer.get_variable(result_idx)); + honk_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 < MM; ++i) { + for (size_t j = 0; j < MM; ++j) { + uint64_t left = static_cast(j); + uint64_t right = static_cast(i); + uint32_t left_idx = plonk_composer.add_variable(fr(left)); + uint32_t right_idx = plonk_composer.add_variable(fr(right)); + uint32_t result_idx = plonk_composer.add_variable(fr(left ^ right)); + + uint32_t add_idx = + plonk_composer.add_variable(fr(left) + fr(right) + plonk_composer.get_variable(result_idx)); + plonk_composer.create_big_add_gate( + { left_idx, right_idx, result_idx, add_idx, fr(1), fr(1), fr(1), fr(-1), fr(0) }); + } + } + + auto honk_prover = honk_composer.create_prover(); + auto plonk_prover = plonk_composer.create_prover(); + + verify_consistency(honk_prover, plonk_prover); +} + +TEST(UltraHonkComposer, test_elliptic_gate) +{ + typedef grumpkin::g1::affine_element affine_element; + typedef grumpkin::g1::element element; + + auto honk_composer = UltraHonkComposer(); + auto plonk_composer = proof_system::plonk::UltraComposer(); + + { + affine_element p1 = crypto::generators::get_generator_data({ 0, 0 }).generator; + affine_element p2 = crypto::generators::get_generator_data({ 0, 1 }).generator; + affine_element p3(element(p1) + element(p2)); + + uint32_t x1 = honk_composer.add_variable(p1.x); + uint32_t y1 = honk_composer.add_variable(p1.y); + uint32_t x2 = honk_composer.add_variable(p2.x); + uint32_t y2 = honk_composer.add_variable(p2.y); + uint32_t x3 = honk_composer.add_variable(p3.x); + uint32_t y3 = honk_composer.add_variable(p3.y); + + ecc_add_gate gate{ x1, y1, x2, y2, x3, y3, 1, 1 }; + honk_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 = honk_composer.add_variable(p3.x); + y3 = honk_composer.add_variable(p3.y); + gate = ecc_add_gate{ x1, y1, x2, y2, x3, y3, beta, 1 }; + honk_composer.create_ecc_add_gate(gate); + + p2_endo.x *= beta; + p3 = affine_element(element(p1) - element(p2_endo)); + x3 = honk_composer.add_variable(p3.x); + y3 = honk_composer.add_variable(p3.y); + gate = ecc_add_gate{ x1, y1, x2, y2, x3, y3, beta.sqr(), -1 }; + honk_composer.create_ecc_add_gate(gate); + } + { + affine_element p1 = crypto::generators::get_generator_data({ 0, 0 }).generator; + affine_element p2 = crypto::generators::get_generator_data({ 0, 1 }).generator; + affine_element p3(element(p1) + element(p2)); + + uint32_t x1 = plonk_composer.add_variable(p1.x); + uint32_t y1 = plonk_composer.add_variable(p1.y); + uint32_t x2 = plonk_composer.add_variable(p2.x); + uint32_t y2 = plonk_composer.add_variable(p2.y); + uint32_t x3 = plonk_composer.add_variable(p3.x); + uint32_t y3 = plonk_composer.add_variable(p3.y); + + ecc_add_gate gate{ x1, y1, x2, y2, x3, y3, 1, 1 }; + plonk_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 = plonk_composer.add_variable(p3.x); + y3 = plonk_composer.add_variable(p3.y); + gate = ecc_add_gate{ x1, y1, x2, y2, x3, y3, beta, 1 }; + plonk_composer.create_ecc_add_gate(gate); + + p2_endo.x *= beta; + p3 = affine_element(element(p1) - element(p2_endo)); + x3 = plonk_composer.add_variable(p3.x); + y3 = plonk_composer.add_variable(p3.y); + gate = ecc_add_gate{ x1, y1, x2, y2, x3, y3, beta.sqr(), -1 }; + plonk_composer.create_ecc_add_gate(gate); + } + + auto honk_prover = honk_composer.create_prover(); + auto plonk_prover = plonk_composer.create_prover(); + + verify_consistency(honk_prover, plonk_prover); +} + +TEST(UltraHonkComposer, non_trivial_tag_permutation) +{ + auto honk_composer = UltraHonkComposer(); + auto plonk_composer = proof_system::plonk::UltraComposer(); + + fr a = fr::random_element(); + { + fr b = -a; + + auto a_idx = honk_composer.add_variable(a); + auto b_idx = honk_composer.add_variable(b); + auto c_idx = honk_composer.add_variable(b); + auto d_idx = honk_composer.add_variable(a); + + honk_composer.create_add_gate( + { a_idx, b_idx, honk_composer.get_zero_idx(), fr::one(), fr::one(), fr::zero(), fr::zero() }); + honk_composer.create_add_gate( + { c_idx, d_idx, honk_composer.get_zero_idx(), fr::one(), fr::one(), fr::zero(), fr::zero() }); + + honk_composer.create_tag(1, 2); + honk_composer.create_tag(2, 1); + + honk_composer.assign_tag(a_idx, 1); + honk_composer.assign_tag(b_idx, 1); + honk_composer.assign_tag(c_idx, 2); + honk_composer.assign_tag(d_idx, 2); + } + { + fr b = -a; + + auto a_idx = plonk_composer.add_variable(a); + auto b_idx = plonk_composer.add_variable(b); + auto c_idx = plonk_composer.add_variable(b); + auto d_idx = plonk_composer.add_variable(a); + + plonk_composer.create_add_gate( + { a_idx, b_idx, plonk_composer.zero_idx, fr::one(), fr::one(), fr::zero(), fr::zero() }); + plonk_composer.create_add_gate( + { c_idx, d_idx, plonk_composer.zero_idx, fr::one(), fr::one(), fr::zero(), fr::zero() }); + + plonk_composer.create_tag(1, 2); + plonk_composer.create_tag(2, 1); + + plonk_composer.assign_tag(a_idx, 1); + plonk_composer.assign_tag(b_idx, 1); + plonk_composer.assign_tag(c_idx, 2); + plonk_composer.assign_tag(d_idx, 2); + } + + auto honk_prover = honk_composer.create_prover(); + auto plonk_prover = plonk_composer.create_prover(); + + verify_consistency(honk_prover, plonk_prover); +} + +TEST(UltraHonkComposer, non_trivial_tag_permutation_and_cycles) +{ + auto honk_composer = UltraHonkComposer(); + auto plonk_composer = proof_system::plonk::UltraComposer(); + + fr a = fr::random_element(); + { + fr c = -a; + + auto a_idx = honk_composer.add_variable(a); + auto b_idx = honk_composer.add_variable(a); + honk_composer.assert_equal(a_idx, b_idx); + auto c_idx = honk_composer.add_variable(c); + auto d_idx = honk_composer.add_variable(c); + honk_composer.assert_equal(c_idx, d_idx); + auto e_idx = honk_composer.add_variable(a); + auto f_idx = honk_composer.add_variable(a); + honk_composer.assert_equal(e_idx, f_idx); + auto g_idx = honk_composer.add_variable(c); + auto h_idx = honk_composer.add_variable(c); + honk_composer.assert_equal(g_idx, h_idx); + + honk_composer.create_tag(1, 2); + honk_composer.create_tag(2, 1); + + honk_composer.assign_tag(a_idx, 1); + honk_composer.assign_tag(c_idx, 1); + honk_composer.assign_tag(e_idx, 2); + honk_composer.assign_tag(g_idx, 2); + + honk_composer.create_add_gate( + { b_idx, a_idx, honk_composer.get_zero_idx(), fr::one(), fr::neg_one(), fr::zero(), fr::zero() }); + honk_composer.create_add_gate( + { c_idx, g_idx, honk_composer.get_zero_idx(), fr::one(), -fr::one(), fr::zero(), fr::zero() }); + honk_composer.create_add_gate( + { e_idx, f_idx, honk_composer.get_zero_idx(), fr::one(), -fr::one(), fr::zero(), fr::zero() }); + } + { + fr c = -a; + + auto a_idx = plonk_composer.add_variable(a); + auto b_idx = plonk_composer.add_variable(a); + plonk_composer.assert_equal(a_idx, b_idx); + auto c_idx = plonk_composer.add_variable(c); + auto d_idx = plonk_composer.add_variable(c); + plonk_composer.assert_equal(c_idx, d_idx); + auto e_idx = plonk_composer.add_variable(a); + auto f_idx = plonk_composer.add_variable(a); + plonk_composer.assert_equal(e_idx, f_idx); + auto g_idx = plonk_composer.add_variable(c); + auto h_idx = plonk_composer.add_variable(c); + plonk_composer.assert_equal(g_idx, h_idx); + + plonk_composer.create_tag(1, 2); + plonk_composer.create_tag(2, 1); + + plonk_composer.assign_tag(a_idx, 1); + plonk_composer.assign_tag(c_idx, 1); + plonk_composer.assign_tag(e_idx, 2); + plonk_composer.assign_tag(g_idx, 2); + + plonk_composer.create_add_gate( + { b_idx, a_idx, plonk_composer.zero_idx, fr::one(), fr::neg_one(), fr::zero(), fr::zero() }); + plonk_composer.create_add_gate( + { c_idx, g_idx, plonk_composer.zero_idx, fr::one(), -fr::one(), fr::zero(), fr::zero() }); + plonk_composer.create_add_gate( + { e_idx, f_idx, plonk_composer.zero_idx, fr::one(), -fr::one(), fr::zero(), fr::zero() }); + } + + auto honk_prover = honk_composer.create_prover(); + auto plonk_prover = plonk_composer.create_prover(); + + verify_consistency(honk_prover, plonk_prover); +} + +TEST(UltraHonkComposer, bad_tag_permutation) +{ + auto honk_composer = UltraHonkComposer(); + auto plonk_composer = proof_system::plonk::UltraComposer(); + + fr a = fr::random_element(); + { + fr b = -a; + + auto a_idx = honk_composer.add_variable(a); + auto b_idx = honk_composer.add_variable(b); + auto c_idx = honk_composer.add_variable(b); + auto d_idx = honk_composer.add_variable(a + 1); + + honk_composer.create_add_gate({ a_idx, b_idx, honk_composer.get_zero_idx(), 1, 1, 0, 0 }); + honk_composer.create_add_gate({ c_idx, d_idx, honk_composer.get_zero_idx(), 1, 1, 0, -1 }); + + honk_composer.create_tag(1, 2); + honk_composer.create_tag(2, 1); + + honk_composer.assign_tag(a_idx, 1); + honk_composer.assign_tag(b_idx, 1); + honk_composer.assign_tag(c_idx, 2); + honk_composer.assign_tag(d_idx, 2); + } + { + fr b = -a; + + auto a_idx = plonk_composer.add_variable(a); + auto b_idx = plonk_composer.add_variable(b); + auto c_idx = plonk_composer.add_variable(b); + auto d_idx = plonk_composer.add_variable(a + 1); + + plonk_composer.create_add_gate({ a_idx, b_idx, plonk_composer.zero_idx, 1, 1, 0, 0 }); + plonk_composer.create_add_gate({ c_idx, d_idx, plonk_composer.zero_idx, 1, 1, 0, -1 }); + + plonk_composer.create_tag(1, 2); + plonk_composer.create_tag(2, 1); + + plonk_composer.assign_tag(a_idx, 1); + plonk_composer.assign_tag(b_idx, 1); + plonk_composer.assign_tag(c_idx, 2); + plonk_composer.assign_tag(d_idx, 2); + } + + auto honk_prover = honk_composer.create_prover(); + auto plonk_prover = plonk_composer.create_prover(); + + verify_consistency(honk_prover, plonk_prover); +} + +TEST(UltraHonkComposer, sort_widget) +{ + auto honk_composer = UltraHonkComposer(); + auto plonk_composer = proof_system::plonk::UltraComposer(); + + { + fr a = fr::one(); + fr b = fr(2); + fr c = fr(3); + fr d = fr(4); + + auto a_idx = honk_composer.add_variable(a); + auto b_idx = honk_composer.add_variable(b); + auto c_idx = honk_composer.add_variable(c); + auto d_idx = honk_composer.add_variable(d); + honk_composer.create_sort_constraint({ a_idx, b_idx, c_idx, d_idx }); + } + { + fr a = fr::one(); + fr b = fr(2); + fr c = fr(3); + fr d = fr(4); + + auto a_idx = plonk_composer.add_variable(a); + auto b_idx = plonk_composer.add_variable(b); + auto c_idx = plonk_composer.add_variable(c); + auto d_idx = plonk_composer.add_variable(d); + plonk_composer.create_sort_constraint({ a_idx, b_idx, c_idx, d_idx }); + } + + auto honk_prover = honk_composer.create_prover(); + auto plonk_prover = plonk_composer.create_prover(); + + verify_consistency(honk_prover, plonk_prover); +} + +TEST(UltraHonkComposer, sort_with_edges_gate) +{ + auto honk_composer = UltraHonkComposer(); + auto plonk_composer = proof_system::plonk::UltraComposer(); + + { + auto idx = add_variables(honk_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 }); + + honk_composer.create_sort_constraint_with_edges(idx, 1, 29); + } + { + auto idx = add_variables(plonk_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 }); + + plonk_composer.create_sort_constraint_with_edges(idx, 1, 29); + } + + auto honk_prover = honk_composer.create_prover(); + auto plonk_prover = plonk_composer.create_prover(); + + verify_consistency(honk_prover, plonk_prover); +} + +TEST(UltraHonkComposer, range_constraint) +{ + auto honk_composer = UltraHonkComposer(); + auto plonk_composer = proof_system::plonk::UltraComposer(); + + { + auto indices = + add_variables(honk_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++) { + honk_composer.create_new_range_constraint(indices[i], 79); + } + honk_composer.create_dummy_constraints(indices); + } + { + auto indices = + add_variables(plonk_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++) { + plonk_composer.create_new_range_constraint(indices[i], 79); + } + plonk_composer.create_dummy_constraints(indices); + } + + auto honk_prover = honk_composer.create_prover(); + auto plonk_prover = plonk_composer.create_prover(); + + verify_consistency(honk_prover, plonk_prover); +} + +TEST(UltraHonkComposer, range_with_gates) +{ + + auto honk_composer = UltraHonkComposer(); + auto plonk_composer = proof_system::plonk::UltraComposer(); + + { + auto idx = add_variables(honk_composer, { 1, 2, 3, 4, 5, 6, 7, 8 }); + for (size_t i = 0; i < idx.size(); i++) { + honk_composer.create_new_range_constraint(idx[i], 8); + } + + honk_composer.create_add_gate( + { idx[0], idx[1], honk_composer.get_zero_idx(), fr::one(), fr::one(), fr::zero(), -3 }); + honk_composer.create_add_gate( + { idx[2], idx[3], honk_composer.get_zero_idx(), fr::one(), fr::one(), fr::zero(), -7 }); + honk_composer.create_add_gate( + { idx[4], idx[5], honk_composer.get_zero_idx(), fr::one(), fr::one(), fr::zero(), -11 }); + honk_composer.create_add_gate( + { idx[6], idx[7], honk_composer.get_zero_idx(), fr::one(), fr::one(), fr::zero(), -15 }); + } + { + auto idx = add_variables(plonk_composer, { 1, 2, 3, 4, 5, 6, 7, 8 }); + for (size_t i = 0; i < idx.size(); i++) { + plonk_composer.create_new_range_constraint(idx[i], 8); + } + + plonk_composer.create_add_gate( + { idx[0], idx[1], plonk_composer.zero_idx, fr::one(), fr::one(), fr::zero(), -3 }); + plonk_composer.create_add_gate( + { idx[2], idx[3], plonk_composer.zero_idx, fr::one(), fr::one(), fr::zero(), -7 }); + plonk_composer.create_add_gate( + { idx[4], idx[5], plonk_composer.zero_idx, fr::one(), fr::one(), fr::zero(), -11 }); + plonk_composer.create_add_gate( + { idx[6], idx[7], plonk_composer.zero_idx, fr::one(), fr::one(), fr::zero(), -15 }); + } + + auto honk_prover = honk_composer.create_prover(); + auto plonk_prover = plonk_composer.create_prover(); + + verify_consistency(honk_prover, plonk_prover); +} + +TEST(UltraHonkComposer, range_with_gates_where_range_is_not_a_power_of_two) +{ + auto honk_composer = UltraHonkComposer(); + auto plonk_composer = proof_system::plonk::UltraComposer(); + + { + auto idx = add_variables(honk_composer, { 1, 2, 3, 4, 5, 6, 7, 8 }); + for (size_t i = 0; i < idx.size(); i++) { + honk_composer.create_new_range_constraint(idx[i], 12); + } + + honk_composer.create_add_gate( + { idx[0], idx[1], honk_composer.get_zero_idx(), fr::one(), fr::one(), fr::zero(), -3 }); + honk_composer.create_add_gate( + { idx[2], idx[3], honk_composer.get_zero_idx(), fr::one(), fr::one(), fr::zero(), -7 }); + honk_composer.create_add_gate( + { idx[4], idx[5], honk_composer.get_zero_idx(), fr::one(), fr::one(), fr::zero(), -11 }); + honk_composer.create_add_gate( + { idx[6], idx[7], honk_composer.get_zero_idx(), fr::one(), fr::one(), fr::zero(), -15 }); + } + { + auto idx = add_variables(plonk_composer, { 1, 2, 3, 4, 5, 6, 7, 8 }); + for (size_t i = 0; i < idx.size(); i++) { + plonk_composer.create_new_range_constraint(idx[i], 12); + } + + plonk_composer.create_add_gate( + { idx[0], idx[1], plonk_composer.zero_idx, fr::one(), fr::one(), fr::zero(), -3 }); + plonk_composer.create_add_gate( + { idx[2], idx[3], plonk_composer.zero_idx, fr::one(), fr::one(), fr::zero(), -7 }); + plonk_composer.create_add_gate( + { idx[4], idx[5], plonk_composer.zero_idx, fr::one(), fr::one(), fr::zero(), -11 }); + plonk_composer.create_add_gate( + { idx[6], idx[7], plonk_composer.zero_idx, fr::one(), fr::one(), fr::zero(), -15 }); + } + + auto honk_prover = honk_composer.create_prover(); + auto plonk_prover = plonk_composer.create_prover(); + + verify_consistency(honk_prover, plonk_prover); +} + +TEST(UltraHonkComposer, sort_widget_complex) +{ + auto honk_composer = UltraHonkComposer(); + auto plonk_composer = proof_system::plonk::UltraComposer(); + + { + 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(honk_composer.add_variable(a[i])); + honk_composer.create_sort_constraint(ind); + } + { + 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(plonk_composer.add_variable(a[i])); + plonk_composer.create_sort_constraint(ind); + } + + auto honk_prover = honk_composer.create_prover(); + auto plonk_prover = plonk_composer.create_prover(); + + verify_consistency(honk_prover, plonk_prover); +} + +TEST(UltraHonkComposer, composed_range_constraint) +{ + auto honk_composer = UltraHonkComposer(); + auto plonk_composer = proof_system::plonk::UltraComposer(); + + auto c = fr::random_element(); + { + auto d = uint256_t(c).slice(0, 133); + auto e = fr(d); + auto a_idx = honk_composer.add_variable(fr(e)); + honk_composer.create_add_gate( + { a_idx, honk_composer.get_zero_idx(), honk_composer.get_zero_idx(), 1, 0, 0, -fr(e) }); + honk_composer.decompose_into_default_range(a_idx, 134); + } + { + auto d = uint256_t(c).slice(0, 133); + auto e = fr(d); + auto a_idx = plonk_composer.add_variable(fr(e)); + plonk_composer.create_add_gate({ a_idx, plonk_composer.zero_idx, plonk_composer.zero_idx, 1, 0, 0, -fr(e) }); + plonk_composer.decompose_into_default_range(a_idx, 134); + } + + auto honk_prover = honk_composer.create_prover(); + auto plonk_prover = plonk_composer.create_prover(); + + verify_consistency(honk_prover, plonk_prover); +} + +TEST(UltraHonkComposer, non_native_field_multiplication) +{ + auto honk_composer = UltraHonkComposer(); + auto plonk_composer = proof_system::plonk::UltraComposer(); + + 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] = honk_composer.add_variable(limbs[0]); + limb_indices[1] = honk_composer.add_variable(limbs[1]); + limb_indices[2] = honk_composer.add_variable(limbs[2]); + limb_indices[3] = honk_composer.add_variable(limbs[3]); + limb_indices[4] = honk_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))); + + proof_system::non_native_field_witnesses inputs{ + a_indices, b_indices, q_indices, r_indices, modulus_limbs, fr(uint256_t(modulus)), + }; + const auto [lo_1_idx, hi_1_idx] = honk_composer.evaluate_non_native_field_multiplication(inputs); + honk_composer.range_constrain_two_limbs(lo_1_idx, hi_1_idx, 70, 70); + } + { + 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] = plonk_composer.add_variable(limbs[0]); + limb_indices[1] = plonk_composer.add_variable(limbs[1]); + limb_indices[2] = plonk_composer.add_variable(limbs[2]); + limb_indices[3] = plonk_composer.add_variable(limbs[3]); + limb_indices[4] = plonk_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))); + + proof_system::plonk::UltraComposer::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] = plonk_composer.evaluate_non_native_field_multiplication(inputs); + plonk_composer.range_constrain_two_limbs(lo_1_idx, hi_1_idx, 70, 70); + } + + auto honk_prover = honk_composer.create_prover(); + auto plonk_prover = plonk_composer.create_prover(); + + verify_consistency(honk_prover, plonk_prover); +} + +TEST(UltraHonkComposer, rom) +{ + auto honk_composer = UltraHonkComposer(); + auto plonk_composer = proof_system::plonk::UltraComposer(); + + auto a = fr::random_element(); + auto b = fr::random_element(); + auto c = fr::random_element(); + auto d = fr::random_element(); + auto e = fr::random_element(); + auto f = fr::random_element(); + auto g = fr::random_element(); + auto h = fr::random_element(); + { + uint32_t rom_values[8]{ + honk_composer.add_variable(a), honk_composer.add_variable(b), honk_composer.add_variable(c), + honk_composer.add_variable(d), honk_composer.add_variable(e), honk_composer.add_variable(f), + honk_composer.add_variable(g), honk_composer.add_variable(h), + }; + + size_t rom_id = honk_composer.create_ROM_array(8); + + for (size_t i = 0; i < 8; ++i) { + honk_composer.set_ROM_element(rom_id, i, rom_values[i]); + } + + uint32_t a_idx = honk_composer.read_ROM_array(rom_id, honk_composer.add_variable(5)); + EXPECT_EQ(a_idx != rom_values[5], true); + uint32_t b_idx = honk_composer.read_ROM_array(rom_id, honk_composer.add_variable(4)); + uint32_t c_idx = honk_composer.read_ROM_array(rom_id, honk_composer.add_variable(1)); + + const auto d_value = + honk_composer.get_variable(a_idx) + honk_composer.get_variable(b_idx) + honk_composer.get_variable(c_idx); + uint32_t d_idx = honk_composer.add_variable(d_value); + + honk_composer.create_big_add_gate({ + a_idx, + b_idx, + c_idx, + d_idx, + 1, + 1, + 1, + -1, + 0, + }); + } + { + uint32_t rom_values[8]{ + plonk_composer.add_variable(a), plonk_composer.add_variable(b), plonk_composer.add_variable(c), + plonk_composer.add_variable(d), plonk_composer.add_variable(e), plonk_composer.add_variable(f), + plonk_composer.add_variable(g), plonk_composer.add_variable(h), + }; + + size_t rom_id = plonk_composer.create_ROM_array(8); + + for (size_t i = 0; i < 8; ++i) { + plonk_composer.set_ROM_element(rom_id, i, rom_values[i]); + } + + uint32_t a_idx = plonk_composer.read_ROM_array(rom_id, plonk_composer.add_variable(5)); + EXPECT_EQ(a_idx != rom_values[5], true); + uint32_t b_idx = plonk_composer.read_ROM_array(rom_id, plonk_composer.add_variable(4)); + uint32_t c_idx = plonk_composer.read_ROM_array(rom_id, plonk_composer.add_variable(1)); + + const auto d_value = plonk_composer.get_variable(a_idx) + plonk_composer.get_variable(b_idx) + + plonk_composer.get_variable(c_idx); + uint32_t d_idx = plonk_composer.add_variable(d_value); + + plonk_composer.create_big_add_gate({ + a_idx, + b_idx, + c_idx, + d_idx, + 1, + 1, + 1, + -1, + 0, + }); + } + + auto honk_prover = honk_composer.create_prover(); + auto plonk_prover = plonk_composer.create_prover(); + + check_consistency(honk_prover, plonk_prover); + verify_consistency(honk_prover, plonk_prover); +} + +TEST(UltraHonkComposer, ram) +{ + auto honk_composer = UltraHonkComposer(); + auto plonk_composer = proof_system::plonk::UltraComposer(); + + auto a = fr::random_element(); + auto b = fr::random_element(); + auto c = fr::random_element(); + auto d = fr::random_element(); + auto e = fr::random_element(); + auto f = fr::random_element(); + auto g = fr::random_element(); + auto h = fr::random_element(); + { + uint32_t ram_values[8]{ + honk_composer.add_variable(a), honk_composer.add_variable(b), honk_composer.add_variable(c), + honk_composer.add_variable(d), honk_composer.add_variable(e), honk_composer.add_variable(f), + honk_composer.add_variable(g), honk_composer.add_variable(h), + }; + + size_t ram_id = honk_composer.create_RAM_array(8); + + for (size_t i = 0; i < 8; ++i) { + honk_composer.init_RAM_element(ram_id, i, ram_values[i]); + } + + uint32_t a_idx = honk_composer.read_RAM_array(ram_id, honk_composer.add_variable(5)); + EXPECT_EQ(a_idx != ram_values[5], true); + + uint32_t b_idx = honk_composer.read_RAM_array(ram_id, honk_composer.add_variable(4)); + uint32_t c_idx = honk_composer.read_RAM_array(ram_id, honk_composer.add_variable(1)); + + honk_composer.write_RAM_array(ram_id, honk_composer.add_variable(4), honk_composer.add_variable(500)); + uint32_t d_idx = honk_composer.read_RAM_array(ram_id, honk_composer.add_variable(4)); + + EXPECT_EQ(honk_composer.get_variable(d_idx), 500); + + // ensure these vars get used in another arithmetic gate + const auto e_value = honk_composer.get_variable(a_idx) + honk_composer.get_variable(b_idx) + + honk_composer.get_variable(c_idx) + honk_composer.get_variable(d_idx); + uint32_t e_idx = honk_composer.add_variable(e_value); + + honk_composer.create_big_add_gate( + { + a_idx, + b_idx, + c_idx, + d_idx, + -1, + -1, + -1, + -1, + 0, + }, + true); + honk_composer.create_big_add_gate( + { + honk_composer.get_zero_idx(), + honk_composer.get_zero_idx(), + honk_composer.get_zero_idx(), + e_idx, + 0, + 0, + 0, + 0, + 0, + }, + false); + } + { + uint32_t ram_values[8]{ + plonk_composer.add_variable(a), plonk_composer.add_variable(b), plonk_composer.add_variable(c), + plonk_composer.add_variable(d), plonk_composer.add_variable(e), plonk_composer.add_variable(f), + plonk_composer.add_variable(g), plonk_composer.add_variable(h), + }; + + size_t ram_id = plonk_composer.create_RAM_array(8); + + for (size_t i = 0; i < 8; ++i) { + plonk_composer.init_RAM_element(ram_id, i, ram_values[i]); + } + + uint32_t a_idx = plonk_composer.read_RAM_array(ram_id, plonk_composer.add_variable(5)); + EXPECT_EQ(a_idx != ram_values[5], true); + + uint32_t b_idx = plonk_composer.read_RAM_array(ram_id, plonk_composer.add_variable(4)); + uint32_t c_idx = plonk_composer.read_RAM_array(ram_id, plonk_composer.add_variable(1)); + + plonk_composer.write_RAM_array(ram_id, plonk_composer.add_variable(4), plonk_composer.add_variable(500)); + uint32_t d_idx = plonk_composer.read_RAM_array(ram_id, plonk_composer.add_variable(4)); + + EXPECT_EQ(plonk_composer.get_variable(d_idx), 500); + + // ensure these vars get used in another arithmetic gate + const auto e_value = plonk_composer.get_variable(a_idx) + plonk_composer.get_variable(b_idx) + + plonk_composer.get_variable(c_idx) + plonk_composer.get_variable(d_idx); + uint32_t e_idx = plonk_composer.add_variable(e_value); + + plonk_composer.create_big_add_gate( + { + a_idx, + b_idx, + c_idx, + d_idx, + -1, + -1, + -1, + -1, + 0, + }, + true); + plonk_composer.create_big_add_gate( + { + plonk_composer.zero_idx, + plonk_composer.zero_idx, + plonk_composer.zero_idx, + e_idx, + 0, + 0, + 0, + 0, + 0, + }, + false); + } + + auto honk_prover = honk_composer.create_prover(); + auto plonk_prover = plonk_composer.create_prover(); + + verify_consistency(honk_prover, plonk_prover); +} + +} // namespace test_ultra_honk_composer diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/proof_system/prover_library.test.cpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/proof_system/prover_library.test.cpp index 09021293709..2dd4b778305 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/proof_system/prover_library.test.cpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/proof_system/prover_library.test.cpp @@ -13,7 +13,6 @@ using namespace proof_system::honk; namespace prover_library_tests { -// field is named Fscalar here because of clash with the Fr template class ProverLibraryTests : public testing::Test { using Polynomial = barretenberg::Polynomial; @@ -206,8 +205,8 @@ template class ProverLibraryTests : public testing::Test { // ∏(s_k + βs_{k+1} + γ(1 + β)) // // in a way that is simple to read (but inefficient). See prover library method for more details. - const Fr eta_sqr = eta.sqr(); - const Fr eta_cube = eta_sqr * eta; + const FF eta_sqr = eta.sqr(); + const FF eta_cube = eta_sqr * eta; std::array accumulators; for (size_t i = 0; i < 4; ++i) { @@ -219,12 +218,12 @@ template class ProverLibraryTests : public testing::Test { // Note: block_mask is used for efficient modulus, i.e. i % N := i & (N-1), for N = 2^k const size_t block_mask = circuit_size - 1; // Initialize 't(X)' to be used in an expression of the form t(X) + β*t(Xω) - Fr table_i = tables[0][0] + tables[1][0] * eta + tables[2][0] * eta_sqr + tables[3][0] * eta_cube; + FF table_i = tables[0][0] + tables[1][0] * eta + tables[2][0] * eta_sqr + tables[3][0] * eta_cube; for (size_t i = 0; i < circuit_size; ++i) { size_t shift_idx = (i + 1) & block_mask; // f = (w_1 + q_2*w_1(Xω)) + η(w_2 + q_m*w_2(Xω)) + η²(w_3 + q_c*w_3(Xω)) + η³q_index. - Fr f_i = (wires[0][i] + wires[0][shift_idx] * column_1_step_size[i]) + + FF f_i = (wires[0][i] + wires[0][shift_idx] * column_1_step_size[i]) + (wires[1][i] + wires[1][shift_idx] * column_2_step_size[i]) * eta + (wires[2][i] + wires[2][shift_idx] * column_3_step_size[i]) * eta_sqr + eta_cube * lookup_index_selector[i]; @@ -233,17 +232,17 @@ template class ProverLibraryTests : public testing::Test { accumulators[0][i] = lookup_selector[i] * f_i + gamma; // t = t_1 + ηt_2 + η²t_3 + η³t_4 - Fr table_i_plus_1 = tables[0][shift_idx] + eta * tables[1][shift_idx] + eta_sqr * tables[2][shift_idx] + + FF table_i_plus_1 = tables[0][shift_idx] + eta * tables[1][shift_idx] + eta_sqr * tables[2][shift_idx] + eta_cube * tables[3][shift_idx]; // t + βt(Xω) + γ(1 + β) - accumulators[1][i] = table_i + table_i_plus_1 * beta + gamma * (Fr::one() + beta); + accumulators[1][i] = table_i + table_i_plus_1 * beta + gamma * (FF::one() + beta); // (1 + β) - accumulators[2][i] = Fr::one() + beta; + accumulators[2][i] = FF::one() + beta; // s + βs(Xω) + γ(1 + β) - accumulators[3][i] = s_lagrange[i] + beta * s_lagrange[shift_idx] + gamma * (Fr::one() + beta); + accumulators[3][i] = s_lagrange[i] + beta * s_lagrange[shift_idx] + gamma * (FF::one() + beta); // Set t(X_i) for next iteration table_i = table_i_plus_1; @@ -303,8 +302,8 @@ template class ProverLibraryTests : public testing::Test { prover_library::compute_sorted_list_accumulator(proving_key, sorted_list_polynomials, eta); // Method 2: Compute local sorted list accumulator simply and inefficiently - const Fr eta_sqr = eta.sqr(); - const Fr eta_cube = eta_sqr * eta; + const FF eta_sqr = eta.sqr(); + const FF eta_cube = eta_sqr * eta; // Compute s = s_1 + η*s_2 + η²*s_3 + η³*s_4 Polynomial sorted_list_accumulator_expected{ sorted_list_polynomials[0] }; diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/proof_system/ultra_prover.cpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/proof_system/ultra_prover.cpp new file mode 100644 index 00000000000..769e744a6d3 --- /dev/null +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/proof_system/ultra_prover.cpp @@ -0,0 +1,56 @@ +#include "ultra_prover.hpp" +#include +#include +#include "barretenberg/honk/proof_system/prover_library.hpp" +#include "barretenberg/honk/sumcheck/sumcheck.hpp" +#include +#include "barretenberg/honk/sumcheck/polynomials/univariate.hpp" // will go away +#include "barretenberg/honk/utils/power_polynomial.hpp" +#include "barretenberg/honk/pcs/commitment_key.hpp" +#include +#include +#include +#include +#include "barretenberg/ecc/curves/bn254/fr.hpp" +#include "barretenberg/ecc/curves/bn254/g1.hpp" +#include "barretenberg/honk/sumcheck/relations/arithmetic_relation.hpp" +#include "barretenberg/honk/sumcheck/relations/grand_product_computation_relation.hpp" +#include "barretenberg/honk/sumcheck/relations/grand_product_initialization_relation.hpp" +#include "barretenberg/polynomials/polynomial.hpp" +#include "barretenberg/honk/flavor/flavor.hpp" +#include "barretenberg/transcript/transcript_wrappers.hpp" +#include +#include "barretenberg/honk/pcs/claim.hpp" + +namespace proof_system::honk { + +/** + * Create UltraHonkProver from proving key, witness and manifest. + * + * @param input_key Proving key. + * @param input_manifest Input manifest + * + * @tparam settings Settings class. + * */ +template +UltraHonkProver::UltraHonkProver(std::vector&& wire_polys, + std::shared_ptr input_key) + : wire_polynomials(wire_polys) + , key(input_key) + , queue(key, transcript) +{} + +template plonk::proof& UltraHonkProver::export_proof() +{ + proof.proof_data = transcript.proof_data; + return proof; +} + +template plonk::proof& UltraHonkProver::construct_proof() +{ + return export_proof(); +} + +template class UltraHonkProver; + +} // namespace proof_system::honk diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/proof_system/ultra_prover.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/proof_system/ultra_prover.hpp new file mode 100644 index 00000000000..b645e5863f3 --- /dev/null +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/proof_system/ultra_prover.hpp @@ -0,0 +1,63 @@ +#pragma once +#include "barretenberg/ecc/curves/bn254/fr.hpp" +#include "barretenberg/honk/pcs/shplonk/shplonk.hpp" +#include "barretenberg/polynomials/polynomial.hpp" +#include "barretenberg/honk/flavor/flavor.hpp" +#include +#include "barretenberg/plonk/proof_system/proving_key/proving_key.hpp" +#include "barretenberg/honk/pcs/commitment_key.hpp" +#include "barretenberg/plonk/proof_system/types/proof.hpp" +#include "barretenberg/plonk/proof_system/types/program_settings.hpp" +#include "barretenberg/honk/pcs/gemini/gemini.hpp" +#include "barretenberg/honk/pcs/shplonk/shplonk_single.hpp" +#include "barretenberg/honk/pcs/kzg/kzg.hpp" +#include "barretenberg/honk/transcript/transcript.hpp" +#include "barretenberg/honk/sumcheck/sumcheck.hpp" +#include "barretenberg/honk/sumcheck/sumcheck_output.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include "barretenberg/honk/pcs/claim.hpp" +#include "barretenberg/honk/proof_system/prover_library.hpp" + +namespace proof_system::honk { + +// TODO(luke): The naming here is awkward. The Standard Honk prover is called "Prover" and aliased as StandardProver. To +// be consistent with that convention outside of the prover class itself, I've called this class UltraHonkProver and use +// the alias UltraProver externally. Resolve. +template class UltraHonkProver { + + using Fr = barretenberg::fr; + using Polynomial = barretenberg::Polynomial; + using Commitment = barretenberg::g1::affine_element; + using POLYNOMIAL = proof_system::honk::StandardArithmetization::POLYNOMIAL; + + public: + UltraHonkProver(std::vector&& wire_polys, + std::shared_ptr input_key = nullptr); + + plonk::proof& export_proof(); + plonk::proof& construct_proof(); + + ProverTranscript transcript; + + std::vector wire_polynomials; + + std::shared_ptr key; + + work_queue queue; + + private: + plonk::proof proof; +}; + +extern template class UltraHonkProver; + +using UltraProver = UltraHonkProver; + +} // namespace proof_system::honk diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/composer/permutation_helper.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/composer/permutation_helper.hpp index 73039533fcb..9685a3eb29c 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/composer/permutation_helper.hpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/composer/permutation_helper.hpp @@ -508,4 +508,25 @@ void compute_plonk_generalized_sigma_permutations(const CircuitConstructor& circ compute_monomial_and_coset_fft_polynomials_from_lagrange("id", key); } +/** + * @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> + */ +// TODO(luke): Consider consolidation of the various "compute sigma permutations" methods which overlap considerably +template +void compute_honk_generalized_sigma_permutations(const CircuitConstructor& circuit_constructor, plonk::proving_key* key) +{ + auto mapping = compute_permutation_mapping(circuit_constructor, key); + + // Compute Plonk-style sigma and ID polynomials from the corresponding mappings + // TODO(luke): Change these to Honk style! (The only difference is we don't need any fancy coset logic). + compute_plonk_permutation_lagrange_polynomials_from_mapping("sigma", mapping.sigmas, key); + compute_plonk_permutation_lagrange_polynomials_from_mapping("id", mapping.ids, key); +} + } // namespace proof_system