Skip to content

Commit

Permalink
Ultra Honk arithmetic and grand product relations (#351)
Browse files Browse the repository at this point in the history
* add width 4 perm grand prod construction relation

* make grand prod construction use id polys, relation correctness passing

* reorganize relation testing suites

* primary ultra arithmetic relation with passing tests

* secondary arith relation and grand prod init relation plus tests
  • Loading branch information
ledwards2225 authored Apr 13, 2023
1 parent d104756 commit 4bfda8f
Show file tree
Hide file tree
Showing 14 changed files with 848 additions and 167 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -201,11 +201,15 @@ std::shared_ptr<plonk::proving_key> UltraHonkComposerHelper<CircuitConstructor>:

construct_lagrange_selector_forms(circuit_constructor, circuit_proving_key.get());

enforce_nonzero_polynomial_selectors(circuit_constructor, circuit_proving_key.get());
// TODO(#217)(luke): Naively enforcing non-zero selectors for Honk will result in some relations not being
// satisfied.
// enforce_nonzero_polynomial_selectors(circuit_constructor, circuit_proving_key.get());

compute_honk_generalized_sigma_permutations<CircuitConstructor::program_width>(circuit_constructor,
circuit_proving_key.get());

compute_first_and_last_lagrange_polynomials(circuit_proving_key.get());

const size_t subgroup_size = circuit_proving_key->circuit_size;

polynomial poly_q_table_column_1(subgroup_size);
Expand Down
103 changes: 0 additions & 103 deletions cpp/src/barretenberg/honk/composer/standard_honk_composer.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -304,109 +304,6 @@ TEST(StandardHonkComposer, VerificationKeyCreation)
composer.circuit_constructor.selectors.size() + composer.num_wires * 2 + 2);
}

/**
* @brief A test taking sumcheck relations and applying them to the witness and selector polynomials to ensure that the
* realtions are correct.
*
* TODO(Kesha): We'll have to update this function once we add zk, since the relation will be incorrect for he first few
* indices
*
*/
TEST(StandardHonkComposer, SumcheckRelationCorrectness)
{
// Create a composer and a dummy circuit with a few gates
StandardHonkComposer composer = StandardHonkComposer();
static const size_t num_wires = StandardHonkComposer::num_wires;
fr a = fr::one();
// Using the public variable to check that public_input_delta is computed and added to the relation correctly
uint32_t a_idx = composer.add_public_variable(a);
fr b = fr::one();
fr c = a + b;
fr d = a + c;
uint32_t b_idx = composer.add_variable(b);
uint32_t c_idx = composer.add_variable(c);
uint32_t d_idx = composer.add_variable(d);
for (size_t i = 0; i < 16; i++) {
composer.create_add_gate({ a_idx, b_idx, c_idx, fr::one(), fr::one(), fr::neg_one(), fr::zero() });
composer.create_add_gate({ d_idx, c_idx, a_idx, fr::one(), fr::neg_one(), fr::neg_one(), fr::zero() });
}
// Create a prover (it will compute proving key and witness)
auto prover = composer.create_prover();

// Generate beta and gamma
fr beta = fr::random_element();
fr gamma = fr::random_element();

// Compute public input delta
const auto public_inputs = composer.circuit_constructor.get_public_inputs();
auto public_input_delta =
honk::compute_public_input_delta<fr>(public_inputs, beta, gamma, prover.key->circuit_size);

sumcheck::RelationParameters<fr> params{
.beta = beta,
.gamma = gamma,
.public_input_delta = public_input_delta,
};

constexpr size_t num_polynomials = proof_system::honk::StandardArithmetization::NUM_POLYNOMIALS;
// Compute grand product polynomial
polynomial z_perm_poly =
prover_library::compute_permutation_grand_product<num_wires>(prover.key, prover.wire_polynomials, beta, gamma);

// Create an array of spans to the underlying polynomials to more easily
// get the transposition.
// Ex: polynomial_spans[3][i] returns the i-th coefficient of the third polynomial
// in the list below
std::array<std::span<const fr>, num_polynomials> evaluations_array;

using POLYNOMIAL = proof_system::honk::StandardArithmetization::POLYNOMIAL;
evaluations_array[POLYNOMIAL::W_L] = prover.wire_polynomials[0];
evaluations_array[POLYNOMIAL::W_R] = prover.wire_polynomials[1];
evaluations_array[POLYNOMIAL::W_O] = prover.wire_polynomials[2];
evaluations_array[POLYNOMIAL::Z_PERM] = z_perm_poly;
evaluations_array[POLYNOMIAL::Z_PERM_SHIFT] = z_perm_poly.shifted();
evaluations_array[POLYNOMIAL::Q_M] = prover.key->polynomial_store.get("q_m_lagrange");
evaluations_array[POLYNOMIAL::Q_L] = prover.key->polynomial_store.get("q_1_lagrange");
evaluations_array[POLYNOMIAL::Q_R] = prover.key->polynomial_store.get("q_2_lagrange");
evaluations_array[POLYNOMIAL::Q_O] = prover.key->polynomial_store.get("q_3_lagrange");
evaluations_array[POLYNOMIAL::Q_C] = prover.key->polynomial_store.get("q_c_lagrange");
evaluations_array[POLYNOMIAL::SIGMA_1] = prover.key->polynomial_store.get("sigma_1_lagrange");
evaluations_array[POLYNOMIAL::SIGMA_2] = prover.key->polynomial_store.get("sigma_2_lagrange");
evaluations_array[POLYNOMIAL::SIGMA_3] = prover.key->polynomial_store.get("sigma_3_lagrange");
evaluations_array[POLYNOMIAL::ID_1] = prover.key->polynomial_store.get("id_1_lagrange");
evaluations_array[POLYNOMIAL::ID_2] = prover.key->polynomial_store.get("id_2_lagrange");
evaluations_array[POLYNOMIAL::ID_3] = prover.key->polynomial_store.get("id_3_lagrange");
evaluations_array[POLYNOMIAL::LAGRANGE_FIRST] = prover.key->polynomial_store.get("L_first_lagrange");
evaluations_array[POLYNOMIAL::LAGRANGE_LAST] = prover.key->polynomial_store.get("L_last_lagrange");

// Construct the round for applying sumcheck relations and results for storing computed results
auto relations = std::tuple(honk::sumcheck::ArithmeticRelation<fr>(),
honk::sumcheck::GrandProductComputationRelation<fr>(),
honk::sumcheck::GrandProductInitializationRelation<fr>());

fr result = 0;
for (size_t i = 0; i < prover.key->circuit_size; i++) {
// Compute an array containing all the evaluations at a given row i
std::array<fr, num_polynomials> evaluations_at_index_i;
for (size_t j = 0; j < num_polynomials; ++j) {
evaluations_at_index_i[j] = evaluations_array[j][i];
}

// For each relation, call the `accumulate_relation_evaluation` over all witness/selector values at the
// i-th row/vertex of the hypercube.
// We use ASSERT_EQ instead of EXPECT_EQ so that the tests stops at the first index at which the result is not
// 0, since result = 0 + C(transposed), which we expect will equal 0.
std::get<0>(relations).add_full_relation_value_contribution(result, evaluations_at_index_i, params);
ASSERT_EQ(result, 0);

std::get<1>(relations).add_full_relation_value_contribution(result, evaluations_at_index_i, params);
ASSERT_EQ(result, 0);

std::get<2>(relations).add_full_relation_value_contribution(result, evaluations_at_index_i, params);
ASSERT_EQ(result, 0);
}
}

TEST(StandardHonkComposer, BaseCase)
{
auto composer = StandardHonkComposer();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,25 @@ std::vector<uint32_t> add_variables(auto& composer, std::vector<fr> variables)
* @param honk_prover
* @param plonk_prover
*/
// NOTE: Currently only checking witness polynomials (wires, sorted lists) and table polys. The permutation polys
// are computed differently between plonk and honk and we do not enforce non-zero selectors in Honk so the final
// element in the selectors will disagree.
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)) {
bool is_sorted_table = (key.find("s_") != std::string::npos);
bool is_table = (key.find("table_value_") != std::string::npos);
if (plonk_store.contains(key) && (is_sorted_table || is_table)) {
ASSERT_EQ(honk_store.get(key), plonk_store.get(key));
}
}

// Check that all wires agree
// Note: for Honk, wires are owned directly by the prover. For Plonk they are stored in the key.
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));
Expand Down
56 changes: 55 additions & 1 deletion cpp/src/barretenberg/honk/flavor/flavor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ struct StandardArithmetization {
* This separation must be maintained to allow for programmatic access, but the ordering of the
* polynomials can be permuted within each category if necessary. Polynomials can also be added
* or removed (assuming consistency with the prover algorithm) but the constants describing the
* number of poynomials in each category must be manually updated.
* number of polynomials in each category must be manually updated.
*
*/
enum POLYNOMIAL {
Expand Down Expand Up @@ -187,4 +187,58 @@ struct StandardHonk {
return output;
}
};

struct UltraArithmetization {
/**
* @brief All of the multivariate polynomials used by the Ultra Honk Prover.
* @details The polynomials are broken into three categories: precomputed, witness, and shifted.
* This separation must be maintained to allow for programmatic access, but the ordering of the
* polynomials can be permuted within each category if necessary. Polynomials can also be added
* or removed (assuming consistency with the prover algorithm) but the constants describing the
* number of polynomials in each category must be manually updated.
*
*/
enum POLYNOMIAL {
/* --- PRECOMPUTED POLYNOMIALS --- */
Q_C,
Q_L,
Q_R,
Q_O,
Q_4,
Q_M,
QARITH,
QSORT,
QELLIPTIC,
QAUX,
QLOOKUPTYPE,
SIGMA_1,
SIGMA_2,
SIGMA_3,
SIGMA_4,
ID_1,
ID_2,
ID_3,
ID_4,
LAGRANGE_FIRST,
LAGRANGE_LAST, // = LAGRANGE_N-1 whithout ZK, but can be less
/* --- WITNESS POLYNOMIALS --- */
W_L,
W_R,
W_O,
W_4,
S_1,
S_2,
S_3,
S_4,
Z_PERM,
Z_LOOKUP,
/* --- SHIFTED POLYNOMIALS --- */
W_1_SHIFT,
W_4_SHIFT,
Z_PERM_SHIFT,
Z_LOOKUP_SHIFT,
/* --- --- */
COUNT // for programmatic determination of NUM_POLYNOMIALS
};
};
} // namespace proof_system::honk
10 changes: 5 additions & 5 deletions cpp/src/barretenberg/honk/proof_system/prover_library.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "prover_library.hpp"
#include "barretenberg/plonk/proof_system/types/prover_settings.hpp"
#include <span>
#include <string>

namespace proof_system::honk::prover_library {

Expand Down Expand Up @@ -55,19 +56,18 @@ Polynomial compute_permutation_grand_product(std::shared_ptr<plonk::proving_key>
// Populate wire and permutation polynomials
std::array<std::span<const Fr>, program_width> wires;
std::array<std::span<const Fr>, program_width> sigmas;
std::array<std::span<const Fr>, program_width> ids;
for (size_t i = 0; i < program_width; ++i) {
std::string sigma_id = "sigma_" + std::to_string(i + 1) + "_lagrange";
wires[i] = wire_polynomials[i];
sigmas[i] = key->polynomial_store.get(sigma_id);
sigmas[i] = key->polynomial_store.get("sigma_" + std::to_string(i + 1) + "_lagrange");
ids[i] = key->polynomial_store.get("id_" + std::to_string(i + 1) + "_lagrange");
}

// Step (1)
// TODO(#222)(kesha): Change the order to engage automatic prefetching and get rid of redundant computation
for (size_t i = 0; i < key->circuit_size; ++i) {
for (size_t k = 0; k < program_width; ++k) {
// Note(luke): this idx could be replaced by proper ID polys if desired
Fr idx = k * key->circuit_size + i;
numerator_accumulator[k][i] = wires[k][i] + (idx * beta) + gamma; // w_k(i) + β.(k*n+i) + γ
numerator_accumulator[k][i] = wires[k][i] + (ids[k][i] * beta) + gamma; // w_k(i) + β.id_k(i) + γ
denominator_accumulator[k][i] = wires[k][i] + (sigmas[k][i] * beta) + gamma; // w_k(i) + β.σ_k(i) + γ
}
}
Expand Down
13 changes: 8 additions & 5 deletions cpp/src/barretenberg/honk/proof_system/prover_library.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,17 @@ template <class FF> class ProverLibraryTests : public testing::Test {
// can simply be random. We're not interested in the particular properties of the result.
std::vector<Polynomial> wires;
std::vector<Polynomial> sigmas;
std::vector<Polynomial> ids;
for (size_t i = 0; i < program_width; ++i) {
wires.emplace_back(get_random_polynomial(num_gates));
sigmas.emplace_back(get_random_polynomial(num_gates));
ids.emplace_back(get_random_polynomial(num_gates));

// Add sigma polys to proving_key; to be used by the prover in constructing it's own z_perm
std::string sigma_id = "sigma_" + std::to_string(i + 1) + "_lagrange";
proving_key->polynomial_store.put(sigma_id, Polynomial{ sigmas[i] });
// Add sigma/ID polys to proving_key; to be used by the prover in constructing it's own z_perm
std::string sigma_label = "sigma_" + std::to_string(i + 1) + "_lagrange";
proving_key->polynomial_store.put(sigma_label, Polynomial{ sigmas[i] });
std::string id_label = "id_" + std::to_string(i + 1) + "_lagrange";
proving_key->polynomial_store.put(id_label, Polynomial{ ids[i] });
}

// Get random challenges
Expand Down Expand Up @@ -108,8 +112,7 @@ template <class FF> class ProverLibraryTests : public testing::Test {
// Step (1)
for (size_t i = 0; i < proving_key->circuit_size; ++i) {
for (size_t k = 0; k < program_width; ++k) {
FF idx = k * proving_key->circuit_size + i; // id_k[i]
numererator_accum[k][i] = wires[k][i] + (idx * beta) + gamma; // w_k(i) + β.(k*n+i) + γ
numererator_accum[k][i] = wires[k][i] + (ids[k][i] * beta) + gamma; // w_k(i) + β.id_k(i) + γ
denominator_accum[k][i] = wires[k][i] + (sigmas[k][i] * beta) + gamma; // w_k(i) + β.σ_k(i) + γ
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,91 @@ template <typename FF> class GrandProductComputationRelation {
(w_2 + beta * sigma_2 + gamma) * (w_3 + beta * sigma_3 + gamma));
};
};

// TODO(luke): With Cody's Flavor work it should be easier to create a simple templated relation
// for handling arbitrary width. For now I'm duplicating the width 3 logic for width 4.
template <typename FF> class UltraGrandProductComputationRelation {
public:
// 1 + polynomial degree of this relation
static constexpr size_t RELATION_LENGTH = 6;
using MULTIVARIATE = proof_system::honk::UltraArithmetization::POLYNOMIAL;

/**
* @brief Compute contribution of the permutation relation for a given edge (internal function)
*
* @details This the relation confirms faithful calculation of the grand
* product polynomial Z_perm.
*
* @param evals transformed to `evals + C(extended_edges(X)...)*scaling_factor`
* @param extended_edges an std::array containing the fully extended Univariate edges.
* @param parameters contains beta, gamma, and public_input_delta, ....
* @param scaling_factor optional term to scale the evaluation before adding to evals.
*/
inline void add_edge_contribution(Univariate<FF, RELATION_LENGTH>& evals,
const auto& extended_edges,
const RelationParameters<FF>& relation_parameters,
const FF& scaling_factor) const
{
const auto& beta = relation_parameters.beta;
const auto& gamma = relation_parameters.gamma;
const auto& public_input_delta = relation_parameters.public_input_delta;

auto w_1 = UnivariateView<FF, RELATION_LENGTH>(extended_edges[MULTIVARIATE::W_L]);
auto w_2 = UnivariateView<FF, RELATION_LENGTH>(extended_edges[MULTIVARIATE::W_R]);
auto w_3 = UnivariateView<FF, RELATION_LENGTH>(extended_edges[MULTIVARIATE::W_O]);
auto w_4 = UnivariateView<FF, RELATION_LENGTH>(extended_edges[MULTIVARIATE::W_4]);
auto sigma_1 = UnivariateView<FF, RELATION_LENGTH>(extended_edges[MULTIVARIATE::SIGMA_1]);
auto sigma_2 = UnivariateView<FF, RELATION_LENGTH>(extended_edges[MULTIVARIATE::SIGMA_2]);
auto sigma_3 = UnivariateView<FF, RELATION_LENGTH>(extended_edges[MULTIVARIATE::SIGMA_3]);
auto sigma_4 = UnivariateView<FF, RELATION_LENGTH>(extended_edges[MULTIVARIATE::SIGMA_4]);
auto id_1 = UnivariateView<FF, RELATION_LENGTH>(extended_edges[MULTIVARIATE::ID_1]);
auto id_2 = UnivariateView<FF, RELATION_LENGTH>(extended_edges[MULTIVARIATE::ID_2]);
auto id_3 = UnivariateView<FF, RELATION_LENGTH>(extended_edges[MULTIVARIATE::ID_3]);
auto id_4 = UnivariateView<FF, RELATION_LENGTH>(extended_edges[MULTIVARIATE::ID_4]);
auto z_perm = UnivariateView<FF, RELATION_LENGTH>(extended_edges[MULTIVARIATE::Z_PERM]);
auto z_perm_shift = UnivariateView<FF, RELATION_LENGTH>(extended_edges[MULTIVARIATE::Z_PERM_SHIFT]);
auto lagrange_first = UnivariateView<FF, RELATION_LENGTH>(extended_edges[MULTIVARIATE::LAGRANGE_FIRST]);
auto lagrange_last = UnivariateView<FF, RELATION_LENGTH>(extended_edges[MULTIVARIATE::LAGRANGE_LAST]);

// Contribution (1)
evals += (((z_perm + lagrange_first) * (w_1 + id_1 * beta + gamma) * (w_2 + id_2 * beta + gamma) *
(w_3 + id_3 * beta + gamma) * (w_4 + id_4 * beta + gamma)) -
((z_perm_shift + lagrange_last * public_input_delta) * (w_1 + sigma_1 * beta + gamma) *
(w_2 + sigma_2 * beta + gamma) * (w_3 + sigma_3 * beta + gamma) * (w_4 + sigma_4 * beta + gamma))) *
scaling_factor;
};

void add_full_relation_value_contribution(FF& full_honk_relation_value,
auto& purported_evaluations,
const RelationParameters<FF>& relation_parameters) const
{
const auto& beta = relation_parameters.beta;
const auto& gamma = relation_parameters.gamma;
const auto& public_input_delta = relation_parameters.public_input_delta;

auto w_1 = purported_evaluations[MULTIVARIATE::W_L];
auto w_2 = purported_evaluations[MULTIVARIATE::W_R];
auto w_3 = purported_evaluations[MULTIVARIATE::W_O];
auto w_4 = purported_evaluations[MULTIVARIATE::W_4];
auto sigma_1 = purported_evaluations[MULTIVARIATE::SIGMA_1];
auto sigma_2 = purported_evaluations[MULTIVARIATE::SIGMA_2];
auto sigma_3 = purported_evaluations[MULTIVARIATE::SIGMA_3];
auto sigma_4 = purported_evaluations[MULTIVARIATE::SIGMA_4];
auto id_1 = purported_evaluations[MULTIVARIATE::ID_1];
auto id_2 = purported_evaluations[MULTIVARIATE::ID_2];
auto id_3 = purported_evaluations[MULTIVARIATE::ID_3];
auto id_4 = purported_evaluations[MULTIVARIATE::ID_4];
auto z_perm = purported_evaluations[MULTIVARIATE::Z_PERM];
auto z_perm_shift = purported_evaluations[MULTIVARIATE::Z_PERM_SHIFT];
auto lagrange_first = purported_evaluations[MULTIVARIATE::LAGRANGE_FIRST];
auto lagrange_last = purported_evaluations[MULTIVARIATE::LAGRANGE_LAST];

// Contribution (1)
full_honk_relation_value +=
((z_perm + lagrange_first) * (w_1 + beta * id_1 + gamma) * (w_2 + beta * id_2 + gamma) *
(w_3 + beta * id_3 + gamma) * (w_4 + beta * id_4 + gamma) -
(z_perm_shift + lagrange_last * public_input_delta) * (w_1 + beta * sigma_1 + gamma) *
(w_2 + beta * sigma_2 + gamma) * (w_3 + beta * sigma_3 + gamma) * (w_4 + beta * sigma_4 + gamma));
};
};
} // namespace proof_system::honk::sumcheck
Loading

0 comments on commit 4bfda8f

Please sign in to comment.