Skip to content

Commit

Permalink
feat: Goblin Translator flavor and permutation correctness (Goblin Tr…
Browse files Browse the repository at this point in the history
…anslator part 7) (#2961)

This PR:
1. Introduces the Goblin Translator flavor with definitions of all
polynomials, etc
2. Adds a relation correctness test for Goblin Translator permutation
3. Adds functions for constructing ordered and concatenated constraint
polynomials used in the permutation
  • Loading branch information
Rumata888 authored Oct 24, 2023
1 parent f23150c commit 737f17f
Show file tree
Hide file tree
Showing 7 changed files with 2,113 additions and 110 deletions.
1,635 changes: 1,635 additions & 0 deletions barretenberg/cpp/src/barretenberg/honk/flavor/goblin_translator.hpp

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -166,4 +166,239 @@ void compute_permutation_grand_products(std::shared_ptr<typename Flavor::Proving
});
}

/**
* @brief Compute new polynomials which are the concatenated versions of other polynomials
*
* @details Multilinear PCS allow to provide openings for concatenated polynomials in an easy way by combining
* commitments. This method creates concatenated version of polynomials we won't need to commit to. Used in Goblin
* Translator
*
* Concatenation in Goblin Translator mean the action of constructing a new Polynomial from existing ones by writing
* their multilinear representations sequentially. For example, if we have f(x₁,x₂)={0, 1, 0, 1} and
* g(x₁,x₂)={1, 0, 0, 1} then h(x₁ ,x₂ ,x₃ )=concatenation(f(x₁,x₂),g(x₁,x₂))={0, 1, 0, 1, 1, 0, 0, 1}
*
* Since we commit to multilinear polynomials with KZG, which treats evaluations as monomial coefficients, in univariate
* form h(x)=f(x)+x⁴⋅g(x)Fr
* @tparam Flavor
* @tparam StorageHandle
* @param proving_key Can be a proving_key or an AllEntities object
*/
template <typename Flavor, typename StorageHandle> void compute_concatenated_polynomials(StorageHandle* proving_key)
{
using PolynomialHandle = typename Flavor::PolynomialHandle;
// Concatenation groups are vectors of polynomials that are concatenated together
std::vector<std::vector<PolynomialHandle>> concatenation_groups = proving_key->get_concatenation_groups();

// Resulting concatenated polynomials
std::vector<PolynomialHandle> targets = proving_key->get_concatenated_constraints();

// A function that produces 1 concatenated polynomial
// TODO(#756): This can be rewritten to use more cores. Currently uses at maximum the number of concatenated
// polynomials (4 in Goblin Translator)
auto ordering_function = [&](size_t i) {
auto my_group = concatenation_groups[i];
auto& current_target = targets[i];

// For each polynomial in group
for (size_t j = 0; j < my_group.size(); j++) {
auto starting_write_offset = current_target.begin();
auto finishing_read_offset = my_group[j].begin();
std::advance(starting_write_offset, j * Flavor::MINI_CIRCUIT_SIZE);
std::advance(finishing_read_offset, Flavor::MINI_CIRCUIT_SIZE);
// Copy into appropriate position in the concatenated polynomial
std::copy(my_group[j].begin(), finishing_read_offset, starting_write_offset);
}
};
parallel_for(concatenation_groups.size(), ordering_function);
}

/**
* @brief Compute denominator polynomials for Goblin Translator's range constraint permutation
*
* @details We need to prove that all the range constraint wires indeed have values within the given range (unless
* changed ∈ [0 , 2¹⁴ - 1]. To do this, we use several virtual concatenated wires, each of which represents a subset
* or original wires (concatenated_range_constraints_<i>). We also generate several new polynomials of the same length
* as concatenated ones. These polynomials have values within range, but they are also constrained by the
* GoblinTranslator's GenPermSort relation, which ensures that sequential values differ by not more than 3, the last
* value is the maximum and the first value is zero (zero at the start allows us not to dance around shifts).
*
* Ideally, we could simply rearrange the values in concatenated_.._0 ,..., concatenated_.._3 and get denominator
* polynomials (ordered_constraints), but we could get the worst case scenario: each value in the polynomials is
* maximum value. What can we do in that case? We still have to add (max_range/3)+1 values to each of the ordered
* wires for the sort constraint to hold. So we also need a and extra denominator to store k ⋅ ( max_range / 3 + 1 )
* values that couldn't go in + ( max_range / 3 + 1 ) connecting values. To counteract the extra ( k + 1 ) ⋅
* ⋅ (max_range / 3 + 1 ) values needed for denominator sort constraints we need a polynomial in the numerator. So we
* can construct a proof when ( k + 1 ) ⋅ ( max_range/ 3 + 1 ) < concatenated size
*
* @tparam Flavor
* @tparam StorageHandle
* @param proving_key
*/
template <typename Flavor, typename StorageHandle>
void compute_goblin_translator_range_constraint_ordered_polynomials(StorageHandle* proving_key)
{

using FF = typename Flavor::FF;

// Get constants
constexpr auto sort_step = Flavor::SORT_STEP;
constexpr auto num_concatenated_wires = Flavor::NUM_CONCATENATED_WIRES;
constexpr auto full_circuit_size = Flavor::FULL_CIRCUIT_SIZE;
constexpr auto mini_circuit_size = Flavor::MINI_CIRCUIT_SIZE;

// The value we have to end polynomials with
constexpr uint32_t max_value = (1 << Flavor::MICRO_LIMB_BITS) - 1;

// Number of elements needed to go from 0 to MAX_VALUE with our step
constexpr size_t sorted_elements_count = (max_value / sort_step) + 1 + (max_value % sort_step == 0 ? 0 : 1);

// Check if we can construct these polynomials
static_assert((num_concatenated_wires + 1) * sorted_elements_count < full_circuit_size);

// First use integers (easier to sort)
std::vector<size_t> sorted_elements(sorted_elements_count);

// Fill with necessary steps
sorted_elements[0] = max_value;
for (size_t i = 1; i < sorted_elements_count; i++) {
sorted_elements[i] = (sorted_elements_count - 1 - i) * sort_step;
}

std::vector<std::vector<uint32_t>> ordered_vectors_uint(num_concatenated_wires);
auto ordered_constraint_polynomials = std::vector{ &proving_key->ordered_range_constraints_0,
&proving_key->ordered_range_constraints_1,
&proving_key->ordered_range_constraints_2,
&proving_key->ordered_range_constraints_3 };
std::vector<size_t> extra_denominator_uint(full_circuit_size);

// Get information which polynomials need to be concatenated
auto concatenation_groups = proving_key->get_concatenation_groups();

// A function that transfers elements from each of the polynomials in the chosen concatenation group in the uint
// ordered polynomials
auto ordering_function = [&](size_t i) {
// Get the group and the main target vector
auto my_group = concatenation_groups[i];
auto& current_vector = ordered_vectors_uint[i];
current_vector.resize(Flavor::FULL_CIRCUIT_SIZE);

// Calculate how much space there is for values from the original polynomials
auto free_space_before_runway = full_circuit_size - sorted_elements_count;

// Calculate the offset of this group's overflowing elements in the extra denominator polynomial
size_t extra_denominator_offset = i * sorted_elements_count;

// Go through each polynomial in the concatenation group
for (size_t j = 0; j < Flavor::CONCATENATION_INDEX; j++) {

// Calculate the offset in the target vector
auto current_offset = j * mini_circuit_size;
// For each element in the polynomial
for (size_t k = 0; k < mini_circuit_size; k++) {

// Put it it the target polynomial
if ((current_offset + k) < free_space_before_runway) {
current_vector[current_offset + k] = static_cast<uint32_t>(uint256_t(my_group[j][k]).data[0]);

// Or in the extra one if there is no space left
} else {
extra_denominator_uint[extra_denominator_offset] =
static_cast<uint32_t>(uint256_t(my_group[j][k]).data[0]);
extra_denominator_offset++;
}
}
}
// Copy the steps into the target polynomial
auto starting_write_offset = current_vector.begin();
std::advance(starting_write_offset, free_space_before_runway);
std::copy(sorted_elements.cbegin(), sorted_elements.cend(), starting_write_offset);

// Sort the polynomial in nondescending order. We sort using vector with size_t elements for 2 reasons:
// 1. It is faster to sort size_t
// 2. Comparison operators for finite fields are operating on internal form, so we'd have to convert them from
// Montgomery
std::sort(current_vector.begin(), current_vector.end());

// Copy the values into the actual polynomial
std::transform(current_vector.cbegin(),
current_vector.cend(),
(*ordered_constraint_polynomials[i]).begin(),
[](uint32_t in) { return FF(in); });
};

// Construct the first 4 polynomials
parallel_for(num_concatenated_wires, ordering_function);
ordered_vectors_uint.clear();

auto sorted_element_insertion_offset = extra_denominator_uint.begin();
std::advance(sorted_element_insertion_offset, num_concatenated_wires * sorted_elements_count);

// Add steps to the extra denominator polynomial
std::copy(sorted_elements.cbegin(), sorted_elements.cend(), sorted_element_insertion_offset);

// Sort it
#ifdef NO_TBB
std::sort(extra_denominator_uint.begin(), extra_denominator_uint.end());
#else
std::sort(std::execution::par_unseq, extra_denominator_uint.begin(), extra_denominator.end());
#endif

// And copy it to the actual polynomial
std::transform(extra_denominator_uint.cbegin(),
extra_denominator_uint.cend(),
proving_key->ordered_range_constraints_4.begin(),
[](uint32_t in) { return FF(in); });
}

/**
* @brief Compute the extra numerator for Goblin range constraint argument
*
* @details Goblin proves that several polynomials contain only values in a certain range through 2 relations:
* 1) A grand product which ignores positions of elements (GoblinTranslatorPermutationRelation)
* 2) A relation enforcing a certain ordering on the elements of the given polynomial
* (GoblinTranslatorGenPermSortRelation)
*
* We take the values from 4 polynomials, and spread them into 5 polynomials + add all the steps from MAX_VALUE to 0. We
* order these polynomials and use them in the denominator of the grand product, at the same time checking that they go
* from MAX_VALUE to 0. To counteract the added steps we also generate an extra range constraint numerator, which
* contains 5 MAX_VALUE, 5 (MAX_VALUE-STEP),... values
*
* @param key Proving key where we will save the polynomials
*/
template <typename Flavor> inline void compute_extra_range_constraint_numerator(auto proving_key)
{

// Get the full goblin circuits size (this is the length of concatenated range constraint polynomials)
auto full_circuit_size = Flavor::FULL_CIRCUIT_SIZE;
auto sort_step = Flavor::SORT_STEP;
auto num_concatenated_wires = Flavor::NUM_CONCATENATED_WIRES;

auto& extra_range_constraint_numerator = proving_key->ordered_extra_range_constraints_numerator;

uint32_t MAX_VALUE = (1 << Flavor::MICRO_LIMB_BITS) - 1;

// Calculate how many elements there are in the sequence MAX_VALUE, MAX_VALUE - 3,...,0
size_t sorted_elements_count = (MAX_VALUE / sort_step) + 1 + (MAX_VALUE % sort_step == 0 ? 0 : 1);

// Check that we can fit every element in the polynomial
ASSERT((num_concatenated_wires + 1) * sorted_elements_count < full_circuit_size);

std::vector<size_t> sorted_elements(sorted_elements_count);

// Calculate the sequence in integers
sorted_elements[0] = MAX_VALUE;
for (size_t i = 1; i < sorted_elements_count; i++) {
sorted_elements[i] = (sorted_elements_count - 1 - i) * sort_step;
}

// TODO(#756): can be parallelized further. This will use at most 5 threads
auto fill_with_shift = [&](size_t shift) {
for (size_t i = 0; i < sorted_elements_count; i++) {
extra_range_constraint_numerator[shift + i * (num_concatenated_wires + 1)] = sorted_elements[i];
}
};
// Fill polynomials with a sequence, where each element is repeated num_concatenated_wires+1 times
parallel_for(num_concatenated_wires + 1, fill_with_shift);
}

} // namespace proof_system::honk::permutation_library
Loading

0 comments on commit 737f17f

Please sign in to comment.