From 98bf76fd13e31e7147d5870cd4d0913e217cf813 Mon Sep 17 00:00:00 2001 From: Suyash Bagad Date: Wed, 3 Aug 2022 17:10:14 +0530 Subject: [PATCH 1/3] HVZK Fix and Quotient Polynomial Refactor (#1197) * wip * Added more base case tests. * Pw/gas limit accounting (#1116) * Initial work * WIP * Account for gas usage when limiting txs per rollup * Minor logging change * Correctly pass through the gas limit * Fixed calculation for max txs per rollup * add cody'ssimple debugging tests in standard_composer.test * making use of large_quotient again to begin debugging * step 1 is passing: quotient pieces with no blinding * Step 2 passing (lnearized): using quotient pieces w/ non-trivial blinding, still using quotient_large for ifft * serial split fft works. * parallel fft on split works. * coset fft, ifft works. * unrolled case works. Co-authored-by: ledwards2225 * msm sizes updated. * some cleanup and debugging code removal, all tests still passing * removing quotient_mid altogether - one more fctn can possibly be removed or simplified * wip quotient split. * quotients match: old case and split case! * standard plonk fix. * Remove quotient_large and correctly compute t_eval. * All but one tests work. * Turbo test fix. * fixes and minor opt. * a couple of comment fixes * more comment fixes and removal of debugging code * One acc test fix: srs size. * uint32_t -> sizet_t * Minor fix in split-evaluate. * Fix acc and other failing tests by correct initialisation of (n+1)th coefficient. * js test fix. * Correct reset proving key fn. * minor reorder. * Ariel's suggestions. Co-authored-by: Ariel * bb.js: account crs fix. * bb.js: crs size fix. * bb.js fix: MSM size adjusted as n or n+1 according to the polynomial. Co-authored-by: Leila Wang * Remove mid_domain and use_mid_for_selectorfft * Remove comments. * Minor edits. * Arijits suggestions. * Added a comment in pkey. * comment correction. Co-authored-by: codygunton Co-authored-by: PhilWindle <60546371+PhilWindle@users.noreply.github.com> Co-authored-by: ledwards2225 Co-authored-by: ledwards2225 Co-authored-by: Ariel --- src/aztec/plonk/composer/composer_base.cpp | 6 +- src/aztec/plonk/composer/composer_base.hpp | 1 - src/aztec/plonk/composer/plookup_composer.cpp | 9 +- .../plonk/composer/standard_composer.hpp | 6 +- .../plonk/composer/standard_composer.test.cpp | 32 ++- src/aztec/plonk/composer/turbo_composer.cpp | 7 +- .../plonk/composer/turbo_composer.test.cpp | 17 +- .../commitment_scheme.test.cpp | 39 +-- .../kate_commitment_scheme.cpp | 45 ++-- src/aztec/plonk/proof_system/constants.hpp | 2 + .../plonk/proof_system/prover/c_bind.cpp | 5 + .../proof_system/prover/c_bind_unrolled.cpp | 6 + .../plonk/proof_system/prover/prover.cpp | 131 ++++++--- .../plonk/proof_system/prover/prover.hpp | 8 +- .../plonk/proof_system/prover/prover.test.cpp | 14 +- .../plonk/proof_system/prover/work_queue.hpp | 30 ++- .../proof_system/proving_key/proving_key.cpp | 74 +++--- .../proof_system/proving_key/proving_key.hpp | 5 +- .../proof_system/types/prover_settings.hpp | 6 - .../permutation_widget_impl.hpp | 17 +- .../random_widgets/plookup_widget_impl.hpp | 4 +- .../transition_widgets/arithmetic_widget.hpp | 1 - .../transition_widgets/elliptic_widget.hpp | 1 - .../genperm_sort_widget.hpp | 1 - .../transition_widgets/transition_widget.hpp | 29 +- .../turbo_arithmetic_widget.hpp | 1 - .../turbo_fixed_base_widget.hpp | 1 - .../transition_widgets/turbo_logic_widget.hpp | 1 - .../transition_widgets/turbo_range_widget.hpp | 1 - .../polynomials/polynomial_arithmetic.cpp | 196 ++++++++++++-- .../polynomials/polynomial_arithmetic.hpp | 18 +- .../polynomial_arithmetic.test.cpp | 250 +++++++++++++----- src/aztec/polynomials/polynomials.bench.cpp | 2 +- src/aztec/rollup/proofs/account/account.cpp | 2 +- .../rollup/proofs/join_split/join_split.cpp | 2 +- 35 files changed, 678 insertions(+), 292 deletions(-) diff --git a/src/aztec/plonk/composer/composer_base.cpp b/src/aztec/plonk/composer/composer_base.cpp index bd6f1ed7ed6..0c1bd3898fa 100644 --- a/src/aztec/plonk/composer/composer_base.cpp +++ b/src/aztec/plonk/composer/composer_base.cpp @@ -259,12 +259,8 @@ std::shared_ptr ComposerBase::compute_proving_key_base(const size_t } poly.ifft(circuit_proving_key->small_domain); polynomial poly_fft(poly, subgroup_size * 4 + 4); + poly_fft.coset_fft(circuit_proving_key->large_domain); - if (properties.use_mid_for_selectorfft) { - poly_fft.coset_fft(circuit_proving_key->mid_domain); - } else { - poly_fft.coset_fft(circuit_proving_key->large_domain); - } circuit_proving_key->constraint_selectors.insert({ properties.name, std::move(poly) }); circuit_proving_key->constraint_selector_ffts.insert({ properties.name + "_fft", std::move(poly_fft) }); } diff --git a/src/aztec/plonk/composer/composer_base.hpp b/src/aztec/plonk/composer/composer_base.hpp index 33657d5e25d..cc1de04d540 100644 --- a/src/aztec/plonk/composer/composer_base.hpp +++ b/src/aztec/plonk/composer/composer_base.hpp @@ -106,7 +106,6 @@ class ComposerBase { public: struct SelectorProperties { std::string name; - bool use_mid_for_selectorfft = false; // use middomain instead of large for selectorfft bool requires_lagrange_base_polynomial = false; // does the prover need the raw lagrange-base selector values? }; diff --git a/src/aztec/plonk/composer/plookup_composer.cpp b/src/aztec/plonk/composer/plookup_composer.cpp index 43f01f1d425..bc9ca587527 100644 --- a/src/aztec/plonk/composer/plookup_composer.cpp +++ b/src/aztec/plonk/composer/plookup_composer.cpp @@ -41,11 +41,10 @@ namespace waffle { std::vector plookup_sel_props() { std::vector result{ - { "q_m", false, true }, { "q_c", false, true }, { "q_1", false, false }, - { "q_2", false, true }, { "q_3", false, false }, { "q_4", false, false }, - { "q_5", false, false }, { "q_arith", false, false }, { "q_ecc_1", false, false }, - { "q_range", false, false }, { "q_sort", false, false }, { "q_logic", false, false }, - { "q_elliptic", false, false }, { "table_index", false, true }, { "table_type", false, true }, + { "q_m", true }, { "q_c", true }, { "q_1", false }, { "q_2", true }, + { "q_3", false }, { "q_4", false }, { "q_5", false }, { "q_arith", false }, + { "q_ecc_1", false }, { "q_range", false }, { "q_sort", false }, { "q_logic", false }, + { "q_elliptic", false }, { "table_index", true }, { "table_type", true }, }; return result; } diff --git a/src/aztec/plonk/composer/standard_composer.hpp b/src/aztec/plonk/composer/standard_composer.hpp index 5419eca831c..e2d8d73727d 100644 --- a/src/aztec/plonk/composer/standard_composer.hpp +++ b/src/aztec/plonk/composer/standard_composer.hpp @@ -13,12 +13,8 @@ enum StandardSelectors { inline std::vector standard_sel_props() { - // We set the use_quotient_mid variable to false in composer settings so as to - // disallow fft computations of size 2n as the degrees of polynomials slighly change - // on introducing the new vanishing polynomial with some roots cut out. std::vector result{ - { "q_m", false, false }, { "q_c", false, false }, { "q_1", false, false }, - { "q_2", false, false }, { "q_3", false, false }, + { "q_m", false }, { "q_c", false }, { "q_1", false }, { "q_2", false }, { "q_3", false }, }; return result; } diff --git a/src/aztec/plonk/composer/standard_composer.test.cpp b/src/aztec/plonk/composer/standard_composer.test.cpp index 1224487b8f1..e359df4f155 100644 --- a/src/aztec/plonk/composer/standard_composer.test.cpp +++ b/src/aztec/plonk/composer/standard_composer.test.cpp @@ -10,6 +10,36 @@ namespace { auto& engine = numeric::random::get_debug_engine(); } +TEST(standard_composer, base_case) +{ + waffle::StandardComposer composer = waffle::StandardComposer(); + fr a = fr::one(); + composer.add_public_variable(a); + + waffle::Prover prover = composer.create_prover(); + waffle::Verifier verifier = composer.create_verifier(); + + waffle::plonk_proof proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); // instance, prover.reference_string.SRS_T2); + EXPECT_EQ(result, true); +} + +TEST(standard_composer, base_case_unrolled) +{ + waffle::StandardComposer composer = waffle::StandardComposer(); + fr a = fr::one(); + composer.add_public_variable(a); + + waffle::UnrolledProver prover = composer.create_unrolled_prover(); + waffle::UnrolledVerifier verifier = composer.create_unrolled_verifier(); + + waffle::plonk_proof proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); // instance, prover.reference_string.SRS_T2); + EXPECT_EQ(result, true); +} + TEST(standard_composer, composer_from_serialized_keys) { waffle::StandardComposer composer = waffle::StandardComposer(); @@ -701,4 +731,4 @@ TEST(standard_composer, test_fixed_group_add_gate) bool result = verifier.verify_proof(proof); EXPECT_EQ(result, true); -} +} \ No newline at end of file diff --git a/src/aztec/plonk/composer/turbo_composer.cpp b/src/aztec/plonk/composer/turbo_composer.cpp index 42a61296f2d..edae4b6d1bb 100644 --- a/src/aztec/plonk/composer/turbo_composer.cpp +++ b/src/aztec/plonk/composer/turbo_composer.cpp @@ -33,10 +33,9 @@ namespace waffle { std::vector turbo_sel_props() { const std::vector result{ - { "q_m", false, false }, { "q_c", false, false }, { "q_1", false, false }, - { "q_2", false, false }, { "q_3", false, false }, { "q_4", false, false }, - { "q_5", false, false }, { "q_arith", false, false }, { "q_ecc_1", false, false }, - { "q_range", false, false }, { "q_logic", false, false }, + { "q_m", false }, { "q_c", false }, { "q_1", false }, { "q_2", false }, + { "q_3", false }, { "q_4", false }, { "q_5", false }, { "q_arith", false }, + { "q_ecc_1", false }, { "q_range", false }, { "q_logic", false }, }; return result; } diff --git a/src/aztec/plonk/composer/turbo_composer.test.cpp b/src/aztec/plonk/composer/turbo_composer.test.cpp index 585412e39fc..aeddd00620e 100644 --- a/src/aztec/plonk/composer/turbo_composer.test.cpp +++ b/src/aztec/plonk/composer/turbo_composer.test.cpp @@ -25,6 +25,21 @@ TEST(turbo_composer, base_case) EXPECT_EQ(result, true); } +TEST(turbo_composer, base_case_unrolled) +{ + waffle::TurboComposer composer = waffle::TurboComposer(); + fr a = fr::one(); + composer.add_public_variable(a); + + waffle::UnrolledTurboProver prover = composer.create_unrolled_prover(); + waffle::UnrolledTurboVerifier verifier = composer.create_unrolled_verifier(); + + waffle::plonk_proof proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); // instance, prover.reference_string.SRS_T2); + EXPECT_EQ(result, true); +} + TEST(turbo_composer, composer_from_serialized_keys) { waffle::TurboComposer composer = waffle::TurboComposer(); @@ -37,7 +52,7 @@ TEST(turbo_composer, composer_from_serialized_keys) auto vk_data = from_buffer(vk_buf); auto crs = std::make_unique("../srs_db"); - auto proving_key = std::make_shared(std::move(pk_data), crs->get_prover_crs(pk_data.n)); + auto proving_key = std::make_shared(std::move(pk_data), crs->get_prover_crs(pk_data.n + 1)); auto verification_key = std::make_shared(std::move(vk_data), crs->get_verifier_crs()); waffle::TurboComposer composer2 = waffle::TurboComposer(proving_key, verification_key); diff --git a/src/aztec/plonk/proof_system/commitment_scheme/commitment_scheme.test.cpp b/src/aztec/plonk/proof_system/commitment_scheme/commitment_scheme.test.cpp index 3b3577d65b2..12626a521b0 100644 --- a/src/aztec/plonk/proof_system/commitment_scheme/commitment_scheme.test.cpp +++ b/src/aztec/plonk/proof_system/commitment_scheme/commitment_scheme.test.cpp @@ -17,15 +17,16 @@ using namespace barretenberg; using namespace waffle; -TEST(commitment_scheme, kate_open) +TEST(commitment_scheme, kate_open) { // generate random polynomial F(X) = coeffs size_t n = 256; - std::vector coeffs(n); + std::vector coeffs(n + 1); for (size_t i = 0; i < n; ++i) { coeffs[i] = fr::random_element(); } std::vector W(coeffs.begin(), coeffs.end()); + coeffs[n] = 0; // generate random evaluation point z fr z = fr::random_element(); @@ -33,7 +34,7 @@ TEST(commitment_scheme, kate_open) // compute opening polynomial W(X), and evaluation f = F(z) transcript::StandardTranscript inp_tx = transcript::StandardTranscript({}); waffle::KateCommitmentScheme newKate; - + // std::shared_ptr crs_factory = (new FileReferenceStringFactory("../srs_db")); auto file_crs = std::make_shared("../srs_db"); auto crs = file_crs->get_prover_crs(n); @@ -47,29 +48,28 @@ TEST(commitment_scheme, kate_open) fr f_y = polynomial_arithmetic::evaluate(&coeffs[0], y, n); fr f = polynomial_arithmetic::evaluate(&coeffs[0], z, n); - newKate.compute_opening_polynomial(&coeffs[0], &W[0], z, n, "W_COMM", fr(0), queue); + newKate.compute_opening_polynomial(&coeffs[0], &W[0], z, n, "W_COMM", fr(0), queue); queue.process_queue(); // check if W(y)(y - z) = F(y) - F(z) - fr w_y = polynomial_arithmetic::evaluate(&W[0], y, n); + fr w_y = polynomial_arithmetic::evaluate(&W[0], y, n - 1); fr y_minus_z = y - z; fr f_y_minus_f = f_y - f; EXPECT_EQ(w_y * y_minus_z, f_y_minus_f); - } -TEST(commitment_scheme, kate_batch_open) +TEST(commitment_scheme, kate_batch_open) { // generate random evaluation points [z_1, z_2, ...] size_t t = 8; - std::vector z_points(t); + std::vector z_points(t); for (size_t k = 0; k < t; ++k) { z_points[k] = fr::random_element(); } - + // generate random polynomials F(X) = coeffs - // + // // z_1 -> [F_{1,1}, F_{1,2}, F_{1, 3}, ..., F_{1, m}] // z_2 -> [F_{2,1}, F_{2,2}, F_{2, 3}, ..., F_{2, m}] // ... @@ -87,11 +87,11 @@ TEST(commitment_scheme, kate_batch_open) } } } - + // setting up the Kate commitment scheme class transcript::StandardTranscript inp_tx = transcript::StandardTranscript({}); waffle::KateCommitmentScheme newKate; - + auto file_crs = std::make_shared("../srs_db"); auto crs = file_crs->get_prover_crs(n); auto circuit_proving_key = std::make_shared(n, 0, crs); @@ -100,9 +100,12 @@ TEST(commitment_scheme, kate_batch_open) // commit to individual polynomials for (size_t k = 0; k < t; ++k) { for (size_t j = 0; j < m; ++j) { - newKate.commit(&coeffs[k * m * n + j * n], "F_{" + std::to_string(k + 1) + ", " + std::to_string(j + 1) + "}", 0, queue); + newKate.commit(&coeffs[k * m * n + j * n], + "F_{" + std::to_string(k + 1) + ", " + std::to_string(j + 1) + "}", + 0, + queue); } - } + } queue.process_queue(); // create random challenges, tags and item_constants @@ -117,7 +120,8 @@ TEST(commitment_scheme, kate_batch_open) // compute opening polynomials W_1, W_2, ..., W_t std::vector W(n * t); - newKate.generic_batch_open(&coeffs[0], &W[0], m, &z_points[0], t, &challenges[0], n, &tags[0], &item_constants[0], queue); + newKate.generic_batch_open( + &coeffs[0], &W[0], m, &z_points[0], t, &challenges[0], n, &tags[0], &item_constants[0], queue); queue.process_queue(); // check if W_{k}(y) * (y - z_k) = \sum_{j} challenge[k]^{j - 1} * [F_{k, j}(y) - F_{k, j}(z_k)] @@ -134,8 +138,8 @@ TEST(commitment_scheme, kate_batch_open) for (size_t j = 0; j < m; ++j) { // compute evaluations of source polynomials at y and z_points - fr f_kj_at_y = polynomial_arithmetic::evaluate(&coeffs[k * m * n + j * n], y, n); - fr f_kj_at_z = polynomial_arithmetic::evaluate(&coeffs[k * m * n + j * n], z_points[k], n); + fr f_kj_at_y = polynomial_arithmetic::evaluate(&coeffs[k * m * n + j * n], y, n); + fr f_kj_at_z = polynomial_arithmetic::evaluate(&coeffs[k * m * n + j * n], z_points[k], n); // compute rhs fr f_term = f_kj_at_y - f_kj_at_z; @@ -146,4 +150,3 @@ TEST(commitment_scheme, kate_batch_open) EXPECT_EQ(lhs, rhs); } } - diff --git a/src/aztec/plonk/proof_system/commitment_scheme/kate_commitment_scheme.cpp b/src/aztec/plonk/proof_system/commitment_scheme/kate_commitment_scheme.cpp index 353650e2ee3..ee6deea2791 100644 --- a/src/aztec/plonk/proof_system/commitment_scheme/kate_commitment_scheme.cpp +++ b/src/aztec/plonk/proof_system/commitment_scheme/kate_commitment_scheme.cpp @@ -34,11 +34,9 @@ void KateCommitmentScheme::compute_opening_polynomial( // We assume that the commitment is well-formed and that there is no remainder term. // Under these conditions we can perform this polynomial division in linear time with good constants. - // Note that if we are using standard plonk, number of roots cut out of vanishing polynomial being 4, - // the degree of the quotient polynomial is 3n. Hence, the degree of the opening polynomial is determined by the - // degree of the polynomial t_{high}(X), which is n. Hence the opening polynomial has (n+1) coefficients. - // We need to change the evaluation domain size accordingly! - fr f = polynomial_arithmetic::evaluate(src, z_point, settings::program_width == 3 ? (n + 1) : n); + // Note that the opening polynomial always has (n+1) coefficients for Standard/Turbo/Ultra due to + // the blinding of the quotient polynomial parts. + fr f = polynomial_arithmetic::evaluate(src, z_point, n + 1); // compute (1 / -z) fr divisor = -z_point.invert(); @@ -189,34 +187,21 @@ void KateCommitmentScheme::batch_open(const transcript::StandardTransc polynomial& shifted_opening_poly = input_key->shifted_opening_poly; if constexpr (!settings::use_linearisation) { - // Add the tuples [t_{mid}(X), \zeta^{n}], [t_{high}(X), \zeta^{2n}]. + // Add the tuples [t_{mid}(X), \zeta^{n}], [t_{high}(X), \zeta^{2n}] // Note: We don't need to include the t_{low}(X) since it is multiplied by 1 for combining with other witness // polynomials. // for (size_t i = 1; i < settings::program_width; ++i) { const size_t offset = i * input_key->small_domain.size; const fr scalar = zeta.pow(static_cast(offset)); - opened_polynomials_at_zeta.push_back({ &input_key->quotient_large[offset], scalar }); - - if (i == settings::program_width - 1 && settings::program_width == 3) { - // We need to add the (3n + 1)-th coefficient of the quotient polynomial t(X) - // to the opening proof polynomial in the case of standard plonk at position (n + 1). - // i.e. opening_poly[n] = \zeta^{2 * n} . t[3 * n] - // - // Note that we add it as a coefficient in the lagrange base form to maintain - // consistency with other coefficients which are to be added in opening_poly. - opening_poly.add_lagrange_base_coefficient(input_key->quotient_large[3 * input_key->n] * scalar); - } - } - } else { - if (settings::program_width == 3) { - opening_poly.add_lagrange_base_coefficient(input_key->linear_poly[input_key->n]); + opened_polynomials_at_zeta.push_back({ &input_key->quotient_polynomial_parts[i][0], scalar }); } } // Add up things to get coefficients of opening polynomials. ITERATE_OVER_DOMAIN_START(input_key->small_domain); - opening_poly[i] = settings::use_linearisation ? input_key->linear_poly[i] : input_key->quotient_large[i]; + opening_poly[i] = + settings::use_linearisation ? input_key->linear_poly[i] : input_key->quotient_polynomial_parts[0][i]; for (const auto& [poly, challenge] : opened_polynomials_at_zeta) { opening_poly[i] += poly[i] * challenge; } @@ -226,6 +211,22 @@ void KateCommitmentScheme::batch_open(const transcript::StandardTransc } ITERATE_OVER_DOMAIN_END; + // Adjust the (n + 1)th coefficient of t_{0,1,2}(X) or r(X) (Note: t_4 (Turbo/Ultra) has only n coefficients) + if (!settings::use_linearisation) { + opening_poly[input_key->n] = 0; + const fr zeta_pow_n = zeta.pow(static_cast(input_key->n)); + + const size_t num_deg_n_poly = + settings::program_width == 3 ? settings::program_width : settings::program_width - 1; + fr scalar_mult = 1; + for (size_t i = 0; i < num_deg_n_poly; i++) { + opening_poly[input_key->n] += input_key->quotient_polynomial_parts[i][input_key->n] * scalar_mult; + scalar_mult *= zeta_pow_n; + } + } else { + opening_poly[input_key->n] = input_key->linear_poly[input_key->n]; + } + // Compute the W_{\zeta}(X) and W_{\zeta \omega}(X) and commitments to them. const auto zeta_omega = zeta * input_key->small_domain.root; diff --git a/src/aztec/plonk/proof_system/constants.hpp b/src/aztec/plonk/proof_system/constants.hpp index 7bc484735aa..067ba919dd8 100644 --- a/src/aztec/plonk/proof_system/constants.hpp +++ b/src/aztec/plonk/proof_system/constants.hpp @@ -6,4 +6,6 @@ namespace waffle { // limb size when simulating a non-native field using bigfield class // (needs to be a universal constant to be used by native verifier) static constexpr uint64_t NUM_LIMB_BITS_IN_FIELD_SIMULATION = 68; + +static constexpr uint32_t NUM_QUOTIENT_PARTS = 4; } // namespace waffle diff --git a/src/aztec/plonk/proof_system/prover/c_bind.cpp b/src/aztec/plonk/proof_system/prover/c_bind.cpp index 81b25a5255a..b183fd0b5c6 100644 --- a/src/aztec/plonk/proof_system/prover/c_bind.cpp +++ b/src/aztec/plonk/proof_system/prover/c_bind.cpp @@ -27,6 +27,11 @@ WASM_EXPORT fr* prover_get_scalar_multiplication_data(waffle::TurboProver* prove return prover->get_scalar_multiplication_data(work_item_number); } +WASM_EXPORT size_t prover_get_scalar_multiplication_size(waffle::TurboProver* prover, size_t work_item_number) +{ + return prover->get_scalar_multiplication_size(work_item_number); +} + WASM_EXPORT void prover_put_scalar_multiplication_data(waffle::TurboProver* prover, g1::element* result, const size_t work_item_number) diff --git a/src/aztec/plonk/proof_system/prover/c_bind_unrolled.cpp b/src/aztec/plonk/proof_system/prover/c_bind_unrolled.cpp index 4b365bb1715..4494ebdf326 100644 --- a/src/aztec/plonk/proof_system/prover/c_bind_unrolled.cpp +++ b/src/aztec/plonk/proof_system/prover/c_bind_unrolled.cpp @@ -28,6 +28,12 @@ WASM_EXPORT fr* unrolled_prover_get_scalar_multiplication_data(waffle::UnrolledT return prover->get_scalar_multiplication_data(work_item_number); } +WASM_EXPORT size_t unrolled_prover_get_scalar_multiplication_size(waffle::UnrolledTurboProver* prover, + size_t work_item_number) +{ + return prover->get_scalar_multiplication_size(work_item_number); +} + WASM_EXPORT void unrolled_prover_put_scalar_multiplication_data(waffle::UnrolledTurboProver* prover, g1::element* result, const size_t work_item_number) diff --git a/src/aztec/plonk/proof_system/prover/prover.cpp b/src/aztec/plonk/proof_system/prover/prover.cpp index 84e26513512..7dfd3e0ea81 100644 --- a/src/aztec/plonk/proof_system/prover/prover.cpp +++ b/src/aztec/plonk/proof_system/prover/prover.cpp @@ -83,7 +83,7 @@ template void ProverBase::compute_wire_pre_commitm std::string wire_tag = "w_" + std::to_string(i + 1); std::string commit_tag = "W_" + std::to_string(i + 1); barretenberg::fr* coefficients = witness->wires.at(wire_tag).get_coefficients(); - commitment_scheme->commit(coefficients, commit_tag, barretenberg::fr(0), queue); + commitment_scheme->commit(coefficients, commit_tag, work_queue::MSMSize::N, queue); } // add public inputs @@ -135,17 +135,14 @@ template void ProverBase::compute_quotient_pre_com // t_{i} would change and so we will have to ensure the correct size of multi-scalar multiplication in // computing the commitments to these polynomials. // - for (size_t i = 0; i < settings::program_width - 1; ++i) { - const size_t offset = n * i; - fr* coefficients = &key->quotient_large.get_coefficients()[offset]; + for (size_t i = 0; i < settings::program_width; ++i) { + fr* coefficients = &key->quotient_polynomial_parts[i].get_coefficients()[0]; std::string quotient_tag = "T_" + std::to_string(i + 1); - commitment_scheme->commit(coefficients, quotient_tag, barretenberg::fr(0), queue); + // Set flag that determines domain size (currently n or n+1) in pippenger (see process_queue()). + // Note: After blinding, all t_i have size n+1 representation (degree n) except t_4 in Turbo/Ultra. + fr domain_size_flag = i > 2 ? work_queue::MSMSize::N : work_queue::MSMSize::N_PLUS_ONE; + commitment_scheme->commit(coefficients, quotient_tag, domain_size_flag, queue); } - - fr* coefficients = &key->quotient_large.get_coefficients()[(settings::program_width - 1) * n]; - std::string quotient_tag = "T_" + std::to_string(settings::program_width); - fr program_flag = settings::program_width == 3 ? barretenberg::fr(1) : barretenberg::fr(0); - commitment_scheme->commit(coefficients, quotient_tag, program_flag, queue); } /** @@ -358,18 +355,24 @@ template void ProverBase::execute_fourth_round() for (auto& widget : transition_widgets) { alpha_base = widget->compute_quotient_contribution(alpha_base, transcript); } - fr* q_mid = &key->quotient_mid[0]; - fr* q_large = &key->quotient_large[0]; #ifdef DEBUG_TIMING start = std::chrono::steady_clock::now(); #endif - if constexpr (settings::uses_quotient_mid) { - barretenberg::polynomial_arithmetic::divide_by_pseudo_vanishing_polynomial( - key->quotient_mid.get_coefficients(), key->small_domain, key->mid_domain); - } + + // The parts of the quotient polynomial t(X) are stored as 4 separate polynomials in + // the code. However, operations such as dividing by the psuedo vanishing polynomial + // as well as iFFT (coset) are to be performed on the polynomial t(X) as a whole. + // We avoid redundant copy of the parts t_1, t_2, t_3, t_4 and instead just tweak the + // relevant functions to work on quotient polynomial parts. + std::vector quotient_poly_parts; + quotient_poly_parts.push_back(&key->quotient_polynomial_parts[0][0]); + quotient_poly_parts.push_back(&key->quotient_polynomial_parts[1][0]); + quotient_poly_parts.push_back(&key->quotient_polynomial_parts[2][0]); + quotient_poly_parts.push_back(&key->quotient_polynomial_parts[3][0]); barretenberg::polynomial_arithmetic::divide_by_pseudo_vanishing_polynomial( - key->quotient_large.get_coefficients(), key->small_domain, key->large_domain); + quotient_poly_parts, key->small_domain, key->large_domain); + #ifdef DEBUG_TIMING end = std::chrono::steady_clock::now(); diff = std::chrono::duration_cast(end - start); @@ -378,23 +381,26 @@ template void ProverBase::execute_fourth_round() #ifdef DEBUG_TIMING start = std::chrono::steady_clock::now(); #endif - if (settings::uses_quotient_mid) { - key->quotient_mid.coset_ifft(key->mid_domain); + polynomial_arithmetic::coset_ifft(quotient_poly_parts, key->large_domain); + + // Manually copy the (n + 1)th coefficient of t_3 for StandardPlonk from t_4. + // This is because the degree of t_3 for StandardPlonk is n. + if (settings::program_width == 3) { + key->quotient_polynomial_parts[2][n] = key->quotient_polynomial_parts[3][0]; + key->quotient_polynomial_parts[3][0] = 0; } - key->quotient_large.coset_ifft(key->large_domain); + #ifdef DEBUG_TIMING end = std::chrono::steady_clock::now(); diff = std::chrono::duration_cast(end - start); std::cerr << "final inverse fourier transforms: " << diff.count() << "ms" << std::endl; #endif - if (settings::uses_quotient_mid) { - ITERATE_OVER_DOMAIN_START(key->mid_domain); - q_large[i] += q_mid[i]; - ITERATE_OVER_DOMAIN_END; - } #ifdef DEBUG_TIMING start = std::chrono::steady_clock::now(); #endif + + add_blinding_to_quotient_polynomial_parts(); + compute_quotient_pre_commitment(); #ifdef DEBUG_TIMING end = std::chrono::steady_clock::now(); @@ -443,40 +449,89 @@ template void ProverBase::compute_linearisation_co alpha_base = widget->compute_linear_contribution(alpha_base, transcript, &r[0]); } // The below code adds −Z_H(z) * (t_lo(X) + z^n * t_mid(X) + z^2n * t_hi(X)) term to r(X) + // (Plus an additional term −Z_H(z) * z^3n * t_highest(X) for Turbo/Ultra) barretenberg::fr z_pow_n = zeta.pow(key->n); barretenberg::fr z_pow_two_n = z_pow_n.sqr(); - // We access Z_H(z) from lagrange_evals + barretenberg::fr z_pow_three_n = z_pow_two_n * z_pow_n; + std::vector quotient_multipliers{ 1, z_pow_n, z_pow_two_n, z_pow_three_n }; + // We access Z_H(z) from lagrange_evals barretenberg::polynomial_arithmetic::lagrange_evaluations lagrange_evals = barretenberg::polynomial_arithmetic::get_lagrange_evaluations(zeta, key->small_domain); + + // First, add to r(X) the contribution associated with the first n coefficients of the quotient + // polynomial parts. This allows multi-threading. The n+1th coefficients are handled separately below. ITERATE_OVER_DOMAIN_START(key->small_domain); - fr quotient_sum = 0, quotient_multiplier = 1; - for (size_t j = 0; j < settings::program_width; j++) { - quotient_sum += key->quotient_large[i + key->n * j] * quotient_multiplier; - quotient_multiplier *= z_pow_n; + fr quotient_sum = 0; + for (size_t j = 0; j < settings::program_width; ++j) { + quotient_sum += key->quotient_polynomial_parts[j][i] * quotient_multipliers[j]; } r[i] += -lagrange_evals.vanishing_poly * quotient_sum; ITERATE_OVER_DOMAIN_END; - // For standard Plonk, t_hi(X) has, (n+1) coefficients - if (settings::program_width == 3) { - if (r.get_size() < key->n + 1) { - r.resize(key->n + 1); - } - r[key->n] = -key->quotient_large[3 * key->n] * lagrange_evals.vanishing_poly * z_pow_two_n; + + // Each t_i for i = 1,2,3 has an n+1th coefficient that must be accounted for in r(X) here. + // Note that t_4 (Turbo/Ultra) always has only n coefficients. + r[key->n] = 0; + const size_t num_deg_n_poly = + settings::program_width == 3 ? settings::program_width : settings::program_width - 1; + for (size_t j = 0; j < num_deg_n_poly; ++j) { + r[key->n] += + -lagrange_evals.vanishing_poly * key->quotient_polynomial_parts[j][key->n] * quotient_multipliers[j]; } // Assert that r(X) at X = zeta is 0 - const auto size = (settings::program_width == 3) ? key->n + 1 : key->n; + const auto size = key->n + 1; fr linear_eval = r.evaluate(zeta, size); // This condition checks if r(z) = 0 but does not abort. if (linear_eval != fr(0)) { info("linear_eval is not 0."); } } else { - fr t_eval = key->quotient_large.evaluate(zeta, 4 * n); + fr t_eval = polynomial_arithmetic::evaluate({ &key->quotient_polynomial_parts[0][0], + &key->quotient_polynomial_parts[1][0], + &key->quotient_polynomial_parts[2][0], + &key->quotient_polynomial_parts[3][0] }, + zeta, + 4 * n); + + // Adjust the evaluation to consider the (n + 1)th coeff. + fr zeta_pow_n = zeta.pow(key->n); + fr scalar = zeta_pow_n; + const size_t num_deg_n_poly = + settings::program_width == 3 ? settings::program_width : settings::program_width - 1; + for (size_t j = 0; j < num_deg_n_poly; j++) { + t_eval += key->quotient_polynomial_parts[j][key->n] * scalar; + scalar *= zeta_pow_n; + } + transcript.add_element("t", t_eval.to_buffer()); } } +// Add blinding to the components in such a way that the full quotient would be unchanged if reconstructed +template void ProverBase::add_blinding_to_quotient_polynomial_parts() +{ + // Construct blinded quotient polynomial parts t_i by adding randomness to the unblinded parts t_i' in + // such a way that the full quotient polynomial t is unchanged upon reconstruction, i.e. + // + // t = t_1' + X^n*t_2' + X^2n*t_3' + X^3n*t_4' = t_1 + X^n*t_2 + X^2n*t_3 + X^3n*t_4 + // + // Blinding is done as follows, where b_i are random field elements: + // + // t_1 = t_1' + b_0*X^n + // t_2 = t_2' - b_0 + b_1*X^n + // t_3 = t_3' - b_1 + b_2*X^n + // t_4 = t_4' - b_2 + // + // For details, please head to: https://hackmd.io/JiyexiqRQJW55TMRrBqp1g. + for (size_t i = 0; i < settings::program_width - 1; i++) { + // Note that only program_width-1 random elements are required for full blinding + fr quotient_randomness = fr::random_element(); + + key->quotient_polynomial_parts[i][key->n] += quotient_randomness; // update coefficient of X^n'th term + key->quotient_polynomial_parts[i + 1][0] -= quotient_randomness; // update constant coefficient + } +} + template waffle::plonk_proof& ProverBase::export_proof() { proof.proof_data = transcript.export_transcript(); diff --git a/src/aztec/plonk/proof_system/prover/prover.hpp b/src/aztec/plonk/proof_system/prover/prover.hpp index 362367cda8b..889243ee772 100644 --- a/src/aztec/plonk/proof_system/prover/prover.hpp +++ b/src/aztec/plonk/proof_system/prover/prover.hpp @@ -33,10 +33,10 @@ template class ProverBase { void compute_batch_opening_polynomials(); void compute_wire_pre_commitments(); void compute_quotient_pre_commitment(); - void init_quotient_polynomials(); void compute_opening_elements(); void compute_linearisation_coefficients(); + void add_blinding_to_quotient_polynomial_parts(); waffle::plonk_proof& export_proof(); waffle::plonk_proof& construct_proof(); @@ -49,6 +49,11 @@ template class ProverBase { return queue.get_scalar_multiplication_data(work_item_number); } + size_t get_scalar_multiplication_size(const size_t work_item_number) const + { + return queue.get_scalar_multiplication_size(work_item_number); + } + barretenberg::fr* get_ifft_data(const size_t work_item_number) const { return queue.get_ifft_data(work_item_number); @@ -91,7 +96,6 @@ template class ProverBase { std::unique_ptr commitment_scheme; work_queue queue; - bool uses_quotient_mid; private: waffle::plonk_proof proof; diff --git a/src/aztec/plonk/proof_system/prover/prover.test.cpp b/src/aztec/plonk/proof_system/prover/prover.test.cpp index d58339f0336..758250207a3 100644 --- a/src/aztec/plonk/proof_system/prover/prover.test.cpp +++ b/src/aztec/plonk/proof_system/prover/prover.test.cpp @@ -259,11 +259,11 @@ waffle::Prover generate_test_data(const size_t n) polynomial q_m_fft(q_m, n * 2); polynomial q_c_fft(q_c, n * 2); - q_1_fft.coset_fft(key->mid_domain); - q_2_fft.coset_fft(key->mid_domain); - q_3_fft.coset_fft(key->mid_domain); - q_m_fft.coset_fft(key->mid_domain); - q_c_fft.coset_fft(key->mid_domain); + q_1_fft.coset_fft(key->large_domain); + q_2_fft.coset_fft(key->large_domain); + q_3_fft.coset_fft(key->large_domain); + q_m_fft.coset_fft(key->large_domain); + q_c_fft.coset_fft(key->large_domain); key->constraint_selectors.insert({ "q_1", std::move(q_l) }); key->constraint_selectors.insert({ "q_2", std::move(q_r) }); @@ -307,7 +307,7 @@ TEST(prover, compute_quotient_polynomial) state.queue.process_queue(); // check that the max degree of our quotient polynomial is 3n - for (size_t i = 3 * n; i < 4 * n; ++i) { - EXPECT_EQ((state.key->quotient_large.at(i) == fr::zero()), true); + for (size_t i = 0; i < n; ++i) { + EXPECT_EQ((state.key->quotient_polynomial_parts[3].at(i) == fr::zero()), true); } } diff --git a/src/aztec/plonk/proof_system/prover/work_queue.hpp b/src/aztec/plonk/proof_system/prover/work_queue.hpp index 43b3f794807..484393a2bcc 100644 --- a/src/aztec/plonk/proof_system/prover/work_queue.hpp +++ b/src/aztec/plonk/proof_system/prover/work_queue.hpp @@ -13,6 +13,7 @@ class work_queue { public: enum WorkType { FFT, SMALL_FFT, IFFT, SCALAR_MULTIPLICATION }; + enum MSMSize { N, N_PLUS_ONE }; struct work_item_info { uint32_t num_scalar_multiplications; @@ -80,6 +81,20 @@ class work_queue { return nullptr; } + size_t get_scalar_multiplication_size(const size_t work_item_number) const + { + size_t count = 0; + for (const auto& item : work_item_queue) { + if (item.work_type == WorkType::SCALAR_MULTIPLICATION) { + if (count == work_item_number) { + return item.constant == MSMSize::N ? key->n : key->n + 1; + } + ++count; + } + } + return 0; + } + barretenberg::fr* get_ifft_data(const size_t work_item_number) const { size_t count = 0; @@ -212,16 +227,10 @@ class work_queue { // most expensive op case WorkType::SCALAR_MULTIPLICATION: { - // If we are using standard plonk, we need a multi-scalar multiplication of size (n + 1) - // to compute the commitment to the polynomial t_{high}(X). The reason for this is explained in - // the comment at location: - // ./src/aztec/plonk/proof_system/prover/prover.cpp/ProverBase::compute_quotient_pre_commitment - // - // To implement this, we use the variable work_item::constant. We set it to `1` if using standard - // plonk and `0` otherwise. Note that we need to change the size of pippenger_runtime_state along - // with the size of the multi-scalar multiplication. - // - if (item.constant == barretenberg::fr(1)) { + // We use the variable work_item::constant to set the size of the multi-scalar multiplication. + // Note that a size (n+1) MSM is always needed to commit to the quotient polynomial parts t_1, t_2 + // and t_3 for Standard/Turbo/Ultra due to the addition of blinding factors + if (item.constant == MSMSize::N_PLUS_ONE) { if (key->reference_string->get_size() < key->small_domain.size + 1) { info("Reference string too small for Pippenger."); } @@ -235,6 +244,7 @@ class work_queue { transcript->add_element(item.tag, result.to_buffer()); } else { + ASSERT(item.constant == MSMSize::N); if (key->reference_string->get_size() < key->small_domain.size) { info("Reference string too small for Pippenger."); } diff --git a/src/aztec/plonk/proof_system/proving_key/proving_key.cpp b/src/aztec/plonk/proof_system/proving_key/proving_key.cpp index 929d1b382c6..d5d9cab47a9 100644 --- a/src/aztec/plonk/proof_system/proving_key/proving_key.cpp +++ b/src/aztec/plonk/proof_system/proving_key/proving_key.cpp @@ -13,6 +13,9 @@ namespace waffle { // the degree of t_{mid}, etc could also increase. Thus, the size of pippenger multi-scalar // multiplications must be changed accordingly! // +// After adding blinding to the quotient polynomial parts, the quotient polynomial parts, the +// linearisation polynomial r(X) as well as opening polynomial W_z(X) are all degree-n (i.e. size n + 1). +// /** * proving_key constructor. * @@ -24,7 +27,6 @@ proving_key::proving_key(const size_t num_gates, : n(num_gates) , num_public_inputs(num_inputs) , small_domain(n, n) - , mid_domain(2 * n, n > min_thread_block ? n : 2 * n) , large_domain(4 * n, n > min_thread_block ? n : 4 * n) , reference_string(crs) , pippenger_runtime_state(n + 1) @@ -42,7 +44,6 @@ proving_key::proving_key(proving_key_data&& data, std::shared_ptr min_thread_block ? n : 2 * n) , large_domain(4 * n, n > min_thread_block ? n : 4 * n) , reference_string(crs) , pippenger_runtime_state(n + 1) @@ -76,30 +77,17 @@ proving_key::proving_key(proving_key_data&& data, std::shared_ptr #include #include +#include #include "../types/polynomial_manifest.hpp" @@ -73,7 +74,6 @@ struct proving_key { std::map wire_ffts; barretenberg::evaluation_domain small_domain; - barretenberg::evaluation_domain mid_domain; barretenberg::evaluation_domain large_domain; std::shared_ptr reference_string; @@ -83,8 +83,7 @@ struct proving_key { barretenberg::polynomial shifted_opening_poly; barretenberg::polynomial linear_poly; - barretenberg::polynomial quotient_mid; - barretenberg::polynomial quotient_large; + barretenberg::polynomial quotient_polynomial_parts[NUM_QUOTIENT_PARTS]; barretenberg::scalar_multiplication::pippenger_runtime_state pippenger_runtime_state; diff --git a/src/aztec/plonk/proof_system/types/prover_settings.hpp b/src/aztec/plonk/proof_system/types/prover_settings.hpp index 925d9f2e210..d16510ca2a2 100644 --- a/src/aztec/plonk/proof_system/types/prover_settings.hpp +++ b/src/aztec/plonk/proof_system/types/prover_settings.hpp @@ -17,7 +17,6 @@ class standard_settings : public settings_base { static constexpr size_t program_width = 3; static constexpr size_t num_shifted_wire_evaluations = 1; static constexpr uint64_t wire_shift_settings = 0b0100; - static constexpr bool uses_quotient_mid = false; static constexpr uint32_t permutation_shift = 30; static constexpr uint32_t permutation_mask = 0xC0000000; static constexpr bool use_linearisation = true; @@ -31,7 +30,6 @@ class unrolled_standard_settings : public settings_base { static constexpr size_t program_width = 3; static constexpr size_t num_shifted_wire_evaluations = 1; static constexpr uint64_t wire_shift_settings = 0b0100; - static constexpr bool uses_quotient_mid = false; static constexpr uint32_t permutation_shift = 30; static constexpr uint32_t permutation_mask = 0xC0000000; static constexpr bool use_linearisation = false; @@ -45,7 +43,6 @@ class turbo_settings : public settings_base { static constexpr size_t program_width = 4; static constexpr size_t num_shifted_wire_evaluations = 4; static constexpr uint64_t wire_shift_settings = 0b1111; - static constexpr bool uses_quotient_mid = false; static constexpr uint32_t permutation_shift = 30; static constexpr uint32_t permutation_mask = 0xC0000000; static constexpr bool use_linearisation = true; @@ -59,7 +56,6 @@ class plookup_settings : public settings_base { static constexpr size_t program_width = 4; static constexpr size_t num_shifted_wire_evaluations = 4; static constexpr uint64_t wire_shift_settings = 0b1111; - static constexpr bool uses_quotient_mid = false; static constexpr uint32_t permutation_shift = 30; static constexpr uint32_t permutation_mask = 0xC0000000; static constexpr bool use_linearisation = true; @@ -73,7 +69,6 @@ class unrolled_plookup_settings : public settings_base { static constexpr size_t program_width = 4; static constexpr size_t num_shifted_wire_evaluations = 4; static constexpr uint64_t wire_shift_settings = 0b1111; - static constexpr bool uses_quotient_mid = false; static constexpr uint32_t permutation_shift = 30; static constexpr uint32_t permutation_mask = 0xC0000000; static constexpr bool use_linearisation = false; @@ -87,7 +82,6 @@ class unrolled_turbo_settings : public settings_base { static constexpr size_t program_width = 4; static constexpr size_t num_shifted_wire_evaluations = 4; static constexpr uint64_t wire_shift_settings = 0b1111; - static constexpr bool uses_quotient_mid = false; static constexpr uint32_t permutation_shift = 30; static constexpr uint32_t permutation_mask = 0xC0000000; static constexpr bool use_linearisation = false; diff --git a/src/aztec/plonk/proof_system/widgets/random_widgets/permutation_widget_impl.hpp b/src/aztec/plonk/proof_system/widgets/random_widgets/permutation_widget_impl.hpp index 6ded9001a71..37afe508169 100644 --- a/src/aztec/plonk/proof_system/widgets/random_widgets/permutation_widget_impl.hpp +++ b/src/aztec/plonk/proof_system/widgets/random_widgets/permutation_widget_impl.hpp @@ -74,15 +74,15 @@ void ProverPermutationWidget 3) { // program_width >= 4 accumulators[6] = &key->shifted_opening_poly[0]; - accumulators[7] = &key->quotient_large[0]; + accumulators[7] = &key->quotient_polynomial_parts[0][0]; } if constexpr (program_width > 4) { // program_width >= 5 accumulators[8] = &key->linear_poly[0]; - accumulators[9] = &key->quotient_large[n]; + accumulators[9] = &key->quotient_polynomial_parts[1][0]; } if constexpr (program_width > 5) { // program_width >= 6 - accumulators[10] = &key->quotient_large[n + n]; - accumulators[11] = &key->quotient_large[n + n + n]; + accumulators[10] = &key->quotient_polynomial_parts[2][0]; + accumulators[11] = &key->quotient_polynomial_parts[3][0]; } for (size_t k = 7; k < program_width; ++k) { // program_width >= 7 // we're out of temporary memory! @@ -338,6 +338,12 @@ barretenberg::fr ProverPermutationWidgetquotient_polynomial_parts[0][key->n] = 0; + key->quotient_polynomial_parts[1][key->n] = 0; + key->quotient_polynomial_parts[2][key->n] = 0; + // Our permutation check boils down to two 'grand product' arguments, // that we represent with a single polynomial Z(X). // We want to test that Z(X) has been constructed correctly. @@ -384,7 +390,6 @@ barretenberg::fr ProverPermutationWidget(public_inputs, beta, gamma, key->small_domain.root); const size_t block_mask = key->large_domain.size - 1; - polynomial& quotient_large = key->quotient_large; // Step 4: Set the quotient polynomial to be equal to // (w_l(X) + \beta.sigma1(X) + \gamma).(w_r(X) + \beta.sigma2(X) + \gamma).(w_o(X) + \beta.sigma3(X) + // \gamma).Z(X).alpha @@ -507,7 +512,7 @@ barretenberg::fr ProverPermutationWidgetquotient_polynomial_parts[i >> key->small_domain.log2_size][i & (key->n - 1)] = T0 * alpha_base; // Update our working root of unity cur_root_times_beta *= key->large_domain.root; diff --git a/src/aztec/plonk/proof_system/widgets/random_widgets/plookup_widget_impl.hpp b/src/aztec/plonk/proof_system/widgets/random_widgets/plookup_widget_impl.hpp index 931d2470790..32b601dca05 100644 --- a/src/aztec/plonk/proof_system/widgets/random_widgets/plookup_widget_impl.hpp +++ b/src/aztec/plonk/proof_system/widgets/random_widgets/plookup_widget_impl.hpp @@ -335,8 +335,6 @@ barretenberg::fr ProverPlookupWidget: fr* lookup_fft = &key->constraint_selector_ffts.at("table_type_fft")[0]; fr* lookup_index_fft = &key->constraint_selector_ffts.at("table_index_fft")[0]; - polynomial& quotient_large = key->quotient_large; - const fr gamma_beta_constant = gamma * (fr(1) + beta); const polynomial& l_1 = key->lagrange_1; @@ -426,7 +424,7 @@ barretenberg::fr ProverPlookupWidget: // Combine into quotient polynomial T0 = numerator - denominator; - quotient_large[i] += T0 * alpha_base; + key->quotient_polynomial_parts[i >> key->small_domain.log2_size][i & (key->n - 1)] += T0 * alpha_base; } } return alpha_base * alpha.sqr() * alpha; diff --git a/src/aztec/plonk/proof_system/widgets/transition_widgets/arithmetic_widget.hpp b/src/aztec/plonk/proof_system/widgets/transition_widgets/arithmetic_widget.hpp index 891a12daae5..c0470c936e2 100644 --- a/src/aztec/plonk/proof_system/widgets/transition_widgets/arithmetic_widget.hpp +++ b/src/aztec/plonk/proof_system/widgets/transition_widgets/arithmetic_widget.hpp @@ -7,7 +7,6 @@ namespace widget { template class ArithmeticKernel { public: - static constexpr bool use_quotient_mid = false; static constexpr size_t num_independent_relations = 1; // We state the challenges required for linear/nonlinear terms computation static constexpr uint8_t quotient_required_challenges = CHALLENGE_BIT_ALPHA; diff --git a/src/aztec/plonk/proof_system/widgets/transition_widgets/elliptic_widget.hpp b/src/aztec/plonk/proof_system/widgets/transition_widgets/elliptic_widget.hpp index 7ec20334676..32cb596e095 100644 --- a/src/aztec/plonk/proof_system/widgets/transition_widgets/elliptic_widget.hpp +++ b/src/aztec/plonk/proof_system/widgets/transition_widgets/elliptic_widget.hpp @@ -7,7 +7,6 @@ namespace widget { template class EllipticKernel { public: - static constexpr bool use_quotient_mid = false; static constexpr size_t num_independent_relations = 4; // We state the challenges required for linear/nonlinear terms computation static constexpr uint8_t quotient_required_challenges = CHALLENGE_BIT_ALPHA; diff --git a/src/aztec/plonk/proof_system/widgets/transition_widgets/genperm_sort_widget.hpp b/src/aztec/plonk/proof_system/widgets/transition_widgets/genperm_sort_widget.hpp index f12f29144d4..dff9132612a 100644 --- a/src/aztec/plonk/proof_system/widgets/transition_widgets/genperm_sort_widget.hpp +++ b/src/aztec/plonk/proof_system/widgets/transition_widgets/genperm_sort_widget.hpp @@ -7,7 +7,6 @@ namespace widget { template class GenPermSortKernel { public: - static constexpr bool use_quotient_mid = false; static constexpr size_t num_independent_relations = 4; // We state the challenges required for linear/nonlinear terms computation static constexpr uint8_t quotient_required_challenges = CHALLENGE_BIT_ALPHA; diff --git a/src/aztec/plonk/proof_system/widgets/transition_widgets/transition_widget.hpp b/src/aztec/plonk/proof_system/widgets/transition_widgets/transition_widget.hpp index 832e5785bf4..2d75e00f5fc 100644 --- a/src/aztec/plonk/proof_system/widgets/transition_widgets/transition_widget.hpp +++ b/src/aztec/plonk/proof_system/widgets/transition_widgets/transition_widget.hpp @@ -339,23 +339,18 @@ class TransitionWidget : public TransitionWidgetBase { poly_ptr_array polynomials = FFTGetter::get_fft_polynomials(key); challenge_array challenges = FFTGetter::get_challenges(transcript, alpha_base, FFTKernel::quotient_required_challenges); - if constexpr (FFTKernel::use_quotient_mid) { - Field* quotient = &key->quotient_mid[0]; - ITERATE_OVER_DOMAIN_START(key->mid_domain); - coefficient_array linear_terms; - FFTKernel::compute_linear_terms(polynomials, challenges, linear_terms, i); - quotient[i] += FFTKernel::sum_linear_terms(polynomials, challenges, linear_terms, i); - FFTKernel::compute_non_linear_terms(polynomials, challenges, quotient[i], i); - ITERATE_OVER_DOMAIN_END; - } else { - Field* quotient = &key->quotient_large[0]; - ITERATE_OVER_DOMAIN_START(key->large_domain); - coefficient_array linear_terms; - FFTKernel::compute_linear_terms(polynomials, challenges, linear_terms, i); - quotient[i] += FFTKernel::sum_linear_terms(polynomials, challenges, linear_terms, i); - FFTKernel::compute_non_linear_terms(polynomials, challenges, quotient[i], i); - ITERATE_OVER_DOMAIN_END; - } + + ITERATE_OVER_DOMAIN_START(key->large_domain); + coefficient_array linear_terms; + FFTKernel::compute_linear_terms(polynomials, challenges, linear_terms, i); + Field sum_of_linear_terms = FFTKernel::sum_linear_terms(polynomials, challenges, linear_terms, i); + + // populate split quotient components + Field& quotient_term = key->quotient_polynomial_parts[i >> key->small_domain.log2_size][i & (key->n - 1)]; + quotient_term += sum_of_linear_terms; + FFTKernel::compute_non_linear_terms(polynomials, challenges, quotient_term, i); + ITERATE_OVER_DOMAIN_END; + return FFTGetter::update_alpha(challenges, FFTKernel::num_independent_relations); } diff --git a/src/aztec/plonk/proof_system/widgets/transition_widgets/turbo_arithmetic_widget.hpp b/src/aztec/plonk/proof_system/widgets/transition_widgets/turbo_arithmetic_widget.hpp index 54c93b15cb2..9d0e2e1adf8 100644 --- a/src/aztec/plonk/proof_system/widgets/transition_widgets/turbo_arithmetic_widget.hpp +++ b/src/aztec/plonk/proof_system/widgets/transition_widgets/turbo_arithmetic_widget.hpp @@ -7,7 +7,6 @@ namespace widget { template class TurboArithmeticKernel { public: - static constexpr bool use_quotient_mid = false; static constexpr size_t num_independent_relations = 2; // We state the challenges required for linear/nonlinear terms computation static constexpr uint8_t quotient_required_challenges = CHALLENGE_BIT_ALPHA; diff --git a/src/aztec/plonk/proof_system/widgets/transition_widgets/turbo_fixed_base_widget.hpp b/src/aztec/plonk/proof_system/widgets/transition_widgets/turbo_fixed_base_widget.hpp index 915426230f5..6939b1f33c5 100644 --- a/src/aztec/plonk/proof_system/widgets/transition_widgets/turbo_fixed_base_widget.hpp +++ b/src/aztec/plonk/proof_system/widgets/transition_widgets/turbo_fixed_base_widget.hpp @@ -73,7 +73,6 @@ namespace widget { **/ template class TurboFixedBaseKernel { public: - static constexpr bool use_quotient_mid = false; static constexpr size_t num_independent_relations = 7; // We state the challenges required for linear/nonlinear terms computation static constexpr uint8_t quotient_required_challenges = CHALLENGE_BIT_ALPHA; diff --git a/src/aztec/plonk/proof_system/widgets/transition_widgets/turbo_logic_widget.hpp b/src/aztec/plonk/proof_system/widgets/transition_widgets/turbo_logic_widget.hpp index d74177cda68..55f6c8f27a2 100644 --- a/src/aztec/plonk/proof_system/widgets/transition_widgets/turbo_logic_widget.hpp +++ b/src/aztec/plonk/proof_system/widgets/transition_widgets/turbo_logic_widget.hpp @@ -7,7 +7,6 @@ namespace widget { template class TurboLogicKernel { public: - static constexpr bool use_quotient_mid = false; static constexpr size_t num_independent_relations = 4; // We state the challenges required for linear/nonlinear terms computation static constexpr uint8_t quotient_required_challenges = CHALLENGE_BIT_ALPHA; diff --git a/src/aztec/plonk/proof_system/widgets/transition_widgets/turbo_range_widget.hpp b/src/aztec/plonk/proof_system/widgets/transition_widgets/turbo_range_widget.hpp index a6cccfa5c92..bd19035c72d 100644 --- a/src/aztec/plonk/proof_system/widgets/transition_widgets/turbo_range_widget.hpp +++ b/src/aztec/plonk/proof_system/widgets/transition_widgets/turbo_range_widget.hpp @@ -64,7 +64,6 @@ namespace widget { **/ template class TurboRangeKernel { public: - static constexpr bool use_quotient_mid = false; static constexpr size_t num_independent_relations = 4; // We state the challenges required for linear/nonlinear terms computation static constexpr uint8_t quotient_required_challenges = CHALLENGE_BIT_ALPHA; diff --git a/src/aztec/polynomials/polynomial_arithmetic.cpp b/src/aztec/polynomials/polynomial_arithmetic.cpp index f746c1788fb..2a93e5309bb 100644 --- a/src/aztec/polynomials/polynomial_arithmetic.cpp +++ b/src/aztec/polynomials/polynomial_arithmetic.cpp @@ -45,6 +45,11 @@ inline uint32_t reverse_bits(uint32_t x, uint32_t bit_length) return (((x >> 16) | (x << 16))) >> (32 - bit_length); } +inline bool is_power_of_two(uint64_t x) +{ + return x && !(x & (x - 1)); +} + void copy_polynomial(fr* src, fr* dest, size_t num_src_coefficients, size_t num_target_coefficients) { // TODO: fiddle around with avx asm to see if we can speed up @@ -56,17 +61,28 @@ void copy_polynomial(fr* src, fr* dest, size_t num_src_coefficients, size_t num_ } } -void fft_inner_serial(fr* coeffs, const size_t domain_size, const std::vector& root_table) +void fft_inner_serial(std::vector coeffs, const size_t domain_size, const std::vector& root_table) { + // Assert that the number of polynomials is a power of two. + const size_t num_polys = coeffs.size(); + ASSERT(is_power_of_two(num_polys)); + const size_t poly_domain_size = domain_size / num_polys; + ASSERT(is_power_of_two(poly_domain_size)); + fr temp; size_t log2_size = (size_t)numeric::get_msb(domain_size); + size_t log2_poly_size = (size_t)numeric::get_msb(poly_domain_size); // efficiently separate odd and even indices - (An introduction to algorithms, section 30.3) for (size_t i = 0; i <= domain_size; ++i) { uint32_t swap_index = (uint32_t)reverse_bits((uint32_t)i, (uint32_t)log2_size); // TODO: should probably use CMOV here insead of an if statement if (i < swap_index) { - fr::__swap(coeffs[i], coeffs[swap_index]); + size_t even_poly_idx = i >> log2_poly_size; + size_t even_elem_idx = i % poly_domain_size; + size_t odd_poly_idx = swap_index >> log2_poly_size; + size_t odd_elem_idx = swap_index % poly_domain_size; + fr::__swap(coeffs[even_poly_idx][even_elem_idx], coeffs[odd_poly_idx][odd_elem_idx]); } } @@ -76,19 +92,26 @@ void fft_inner_serial(fr* coeffs, const size_t domain_size, const std::vector> log2_poly_size; + const size_t even_elem_idx = (k + j) & (poly_domain_size - 1); + const size_t odd_poly_idx = (k + j + m) >> log2_poly_size; + const size_t odd_elem_idx = (k + j + m) & (poly_domain_size - 1); + + temp = root_table[i - 1][j] * coeffs[odd_poly_idx][odd_elem_idx]; + coeffs[odd_poly_idx][odd_elem_idx] = coeffs[even_poly_idx][even_elem_idx] - temp; + coeffs[even_poly_idx][even_elem_idx] += temp; } } } @@ -146,10 +169,21 @@ void compute_multiplicative_subgroup(const size_t log2_subgroup_size, } } -void fft_inner_parallel(fr* coeffs, const evaluation_domain& domain, const fr&, const std::vector& root_table) +void fft_inner_parallel(std::vector coeffs, + const evaluation_domain& domain, + const fr&, + const std::vector& root_table) { // hmm // fr* scratch_space = (fr*)aligned_alloc(64, sizeof(fr) * domain.size); fr* scratch_space = get_scratch_space(domain.size); + + const size_t num_polys = coeffs.size(); + ASSERT(is_power_of_two(num_polys)); + const size_t poly_size = domain.size / num_polys; + ASSERT(is_power_of_two(poly_size)); + const size_t poly_mask = poly_size - 1; + const size_t log2_poly_size = (size_t)numeric::get_msb(poly_size); + #ifndef NO_MULTITHREADING #pragma omp parallel #endif @@ -171,8 +205,13 @@ void fft_inner_parallel(fr* coeffs, const evaluation_domain& domain, const fr&, uint32_t swap_index_1 = (uint32_t)reverse_bits((uint32_t)i, (uint32_t)domain.log2_size); uint32_t swap_index_2 = (uint32_t)reverse_bits((uint32_t)i + 1, (uint32_t)domain.log2_size); - fr::__copy(coeffs[swap_index_1], temp_1); - fr::__copy(coeffs[swap_index_2], temp_2); + size_t poly_idx_1 = swap_index_1 >> log2_poly_size; + size_t elem_idx_1 = swap_index_1 & poly_mask; + size_t poly_idx_2 = swap_index_2 >> log2_poly_size; + size_t elem_idx_2 = swap_index_2 & poly_mask; + + fr::__copy(coeffs[poly_idx_1][elem_idx_1], temp_1); + fr::__copy(coeffs[poly_idx_2][elem_idx_2], temp_2); scratch_space[i + 1] = temp_1 - temp_2; scratch_space[i] = temp_1 + temp_2; } @@ -181,8 +220,8 @@ void fft_inner_parallel(fr* coeffs, const evaluation_domain& domain, const fr&, // hard code exception for when the domain size is tiny - we won't execute the next loop, so need to manually // reduce + copy if (domain.size <= 2) { - coeffs[0] = scratch_space[0]; - coeffs[1] = scratch_space[1]; + coeffs[0][0] = scratch_space[0]; + coeffs[0][1] = scratch_space[1]; } // outer FFT loop @@ -253,9 +292,15 @@ void fft_inner_parallel(fr* coeffs, const evaluation_domain& domain, const fr&, for (size_t i = start; i < end; ++i) { size_t k1 = (i & index_mask) << 1; size_t j1 = i & block_mask; + + size_t poly_idx_1 = (k1 + j1) >> log2_poly_size; + size_t elem_idx_1 = (k1 + j1) & poly_mask; + size_t poly_idx_2 = (k1 + j1 + m) >> log2_poly_size; + size_t elem_idx_2 = (k1 + j1 + m) & poly_mask; + temp = round_roots[j1] * scratch_space[k1 + j1 + m]; - coeffs[k1 + j1 + m] = scratch_space[k1 + j1] - temp; - coeffs[k1 + j1] = scratch_space[k1 + j1] + temp; + coeffs[poly_idx_2][elem_idx_2] = scratch_space[k1 + j1] - temp; + coeffs[poly_idx_1][elem_idx_1] = scratch_space[k1 + j1] + temp; } } } @@ -382,20 +427,41 @@ void fft_inner_parallel( void fft(fr* coeffs, const evaluation_domain& domain) { - fft_inner_parallel(coeffs, domain, domain.root, domain.get_round_roots()); + fft_inner_parallel({ coeffs }, domain, domain.root, domain.get_round_roots()); +} + +void fft(std::vector coeffs, const evaluation_domain& domain) +{ + fft_inner_parallel(coeffs, domain.size, domain.root, domain.get_round_roots()); } void ifft(fr* coeffs, const evaluation_domain& domain) { - fft_inner_parallel(coeffs, domain, domain.root_inverse, domain.get_inverse_round_roots()); + fft_inner_parallel({ coeffs }, domain, domain.root_inverse, domain.get_inverse_round_roots()); ITERATE_OVER_DOMAIN_START(domain); coeffs[i] *= domain.domain_inverse; ITERATE_OVER_DOMAIN_END; } +void ifft(std::vector coeffs, const evaluation_domain& domain) +{ + fft_inner_parallel(coeffs, domain, domain.root_inverse, domain.get_inverse_round_roots()); + + const size_t num_polys = coeffs.size(); + ASSERT(is_power_of_two(num_polys)); + const size_t poly_size = domain.size / num_polys; + ASSERT(is_power_of_two(poly_size)); + const size_t poly_mask = poly_size - 1; + const size_t log2_poly_size = (size_t)numeric::get_msb(poly_size); + + ITERATE_OVER_DOMAIN_START(domain); + coeffs[i >> log2_poly_size][i & poly_mask] *= domain.domain_inverse; + ITERATE_OVER_DOMAIN_END; +} + void fft_with_constant(fr* coeffs, const evaluation_domain& domain, const fr& value) { - fft_inner_parallel(coeffs, domain, domain.root, domain.get_round_roots()); + fft_inner_parallel({ coeffs }, domain, domain.root, domain.get_round_roots()); ITERATE_OVER_DOMAIN_START(domain); coeffs[i] *= value; ITERATE_OVER_DOMAIN_END; @@ -407,6 +473,21 @@ void coset_fft(fr* coeffs, const evaluation_domain& domain) fft(coeffs, domain); } +void coset_fft(std::vector coeffs, const evaluation_domain& domain) +{ + const size_t num_polys = coeffs.size(); + ASSERT(is_power_of_two(num_polys)); + const size_t poly_size = domain.size / num_polys; + const fr generator_pow_n = domain.generator.pow(poly_size); + fr generator_start = 1; + + for (size_t i = 0; i < num_polys; i++) { + scale_by_generator(coeffs[i], coeffs[i], domain, generator_start, domain.generator, poly_size); + generator_start *= generator_pow_n; + } + fft(coeffs, domain); +} + void coset_fft(fr* coeffs, const evaluation_domain& domain, const evaluation_domain&, const size_t domain_extension) { const size_t log2_domain_extension = static_cast(numeric::get_msb(domain_extension)); @@ -479,7 +560,7 @@ void coset_fft_with_generator_shift(fr* coeffs, const evaluation_domain& domain, void ifft_with_constant(fr* coeffs, const evaluation_domain& domain, const fr& value) { - fft_inner_parallel(coeffs, domain, domain.root_inverse, domain.get_inverse_round_roots()); + fft_inner_parallel({ coeffs }, domain, domain.root_inverse, domain.get_inverse_round_roots()); fr T0 = domain.domain_inverse * value; ITERATE_OVER_DOMAIN_START(domain); coeffs[i] *= T0; @@ -492,6 +573,22 @@ void coset_ifft(fr* coeffs, const evaluation_domain& domain) scale_by_generator(coeffs, coeffs, domain, fr::one(), domain.generator_inverse, domain.size); } +void coset_ifft(std::vector coeffs, const evaluation_domain& domain) +{ + ifft(coeffs, domain); + + const size_t num_polys = coeffs.size(); + ASSERT(is_power_of_two(num_polys)); + const size_t poly_size = domain.size / num_polys; + const fr generator_inv_pow_n = domain.generator_inverse.pow(poly_size); + fr generator_start = 1; + + for (size_t i = 0; i < num_polys; i++) { + scale_by_generator(coeffs[i], coeffs[i], domain, generator_start, domain.generator_inverse, poly_size); + generator_start *= generator_inv_pow_n; + } +} + void add(const fr* a_coeffs, const fr* b_coeffs, fr* r_coeffs, const evaluation_domain& domain) { ITERATE_OVER_DOMAIN_START(domain); @@ -546,6 +643,43 @@ fr evaluate(const fr* coeffs, const fr& z, const size_t n) return r; } +fr evaluate(const std::vector coeffs, const fr& z, const size_t large_n) +{ + const size_t num_polys = coeffs.size(); + const size_t poly_size = large_n / num_polys; + ASSERT(is_power_of_two(poly_size)); + const size_t log2_poly_size = (size_t)numeric::get_msb(poly_size); +#ifndef NO_MULTITHREADING + size_t num_threads = max_threads::compute_num_threads(); +#else + size_t num_threads = 1; +#endif + size_t range_per_thread = large_n / num_threads; + size_t leftovers = large_n - (range_per_thread * num_threads); + fr* evaluations = new fr[num_threads]; +#ifndef NO_MULTITHREADING +#pragma omp parallel for +#endif + for (size_t j = 0; j < num_threads; ++j) { + fr z_acc = z.pow(static_cast(j * range_per_thread)); + size_t offset = j * range_per_thread; + evaluations[j] = fr::zero(); + size_t end = (j == num_threads - 1) ? offset + range_per_thread + leftovers : offset + range_per_thread; + for (size_t i = offset; i < end; ++i) { + fr work_var = z_acc * coeffs[i >> log2_poly_size][i & (poly_size - 1)]; + evaluations[j] += work_var; + z_acc *= z; + } + } + + fr r = fr::zero(); + for (size_t j = 0; j < num_threads; ++j) { + r += evaluations[j]; + } + delete[] evaluations; + return r; +} + // For L_1(X) = (X^{n} - 1 / (X - 1)) * (1 / n) // Compute the 2n-fft of L_1(X) // We can use this to compute the 2n-fft evaluations of any L_i(X). @@ -634,7 +768,7 @@ void compute_lagrange_polynomial_fft(fr* l_1_coefficients, delete[] subgroup_roots; } -void divide_by_pseudo_vanishing_polynomial(fr* fft_point_evaluations, +void divide_by_pseudo_vanishing_polynomial(std::vector coeffs, const evaluation_domain& src_domain, const evaluation_domain& target_domain, const size_t num_roots_cut_out_of_vanishing_polynomial) @@ -662,6 +796,14 @@ void divide_by_pseudo_vanishing_polynomial(fr* fft_point_evaluations, // NOTE: If in future, there arises a need to cut off more zeros, this method will not require any changes. // + // Assert that the number of polynomials in coeffs is a power of 2. + const size_t num_polys = coeffs.size(); + ASSERT(is_power_of_two(num_polys)); + const size_t poly_size = target_domain.size / num_polys; + ASSERT(is_power_of_two(poly_size)); + const size_t poly_mask = poly_size - 1; + const size_t log2_poly_size = (size_t)numeric::get_msb(poly_size); + // `fft_point_evaluations` should be in point-evaluation form, evaluated at the 4n'th roots of unity mulitplied by // `target_domain`'s coset generator P(X) = X^n - 1 will form a subgroup of order 4 when evaluated at these points // If X = w^i, P(X) = 1 @@ -701,10 +843,12 @@ void divide_by_pseudo_vanishing_polynomial(fr* fft_point_evaluations, fr work_root = src_domain.generator; for (size_t i = 0; i < target_domain.size; i += subgroup_size) { for (size_t j = 0; j < subgroup_size; ++j) { - fft_point_evaluations[i + j] *= subgroup_roots[j]; + size_t poly_idx = (i + j) >> log2_poly_size; + size_t elem_idx = (i + j) & poly_mask; + coeffs[poly_idx][elem_idx] *= subgroup_roots[j]; for (size_t k = 0; k < num_roots_cut_out_of_vanishing_polynomial; ++k) { - fft_point_evaluations[i + j] *= work_root + numerator_constants[k]; + coeffs[poly_idx][elem_idx] *= work_root + numerator_constants[k]; } work_root *= target_domain.root; } @@ -719,10 +863,12 @@ void divide_by_pseudo_vanishing_polynomial(fr* fft_point_evaluations, fr work_root = src_domain.generator * root_shift; for (size_t i = offset; i < offset + target_domain.thread_size; i += subgroup_size) { for (size_t j = 0; j < subgroup_size; ++j) { - fft_point_evaluations[i + j] *= subgroup_roots[j]; + size_t poly_idx = (i + j) >> log2_poly_size; + size_t elem_idx = (i + j) & poly_mask; + coeffs[poly_idx][elem_idx] *= subgroup_roots[j]; for (size_t k = 0; k < num_roots_cut_out_of_vanishing_polynomial; ++k) { - fft_point_evaluations[i + j] *= work_root + numerator_constants[k]; + coeffs[poly_idx][elem_idx] *= work_root + numerator_constants[k]; } work_root *= target_domain.root; diff --git a/src/aztec/polynomials/polynomial_arithmetic.hpp b/src/aztec/polynomials/polynomial_arithmetic.hpp index e3e13ae57ea..1600b4f470d 100644 --- a/src/aztec/polynomials/polynomial_arithmetic.hpp +++ b/src/aztec/polynomials/polynomial_arithmetic.hpp @@ -14,16 +14,22 @@ struct lagrange_evaluations { }; fr evaluate(const fr* coeffs, const fr& z, const size_t n); +fr evaluate(const std::vector coeffs, const fr& z, const size_t large_n); void copy_polynomial(fr* src, fr* dest, size_t num_src_coefficients, size_t num_target_coefficients); // 2. Compute a lookup table of the roots of unity, and suffer through cache misses from nonlinear access patterns -void fft_inner_serial(fr* coeffs, const size_t domain_size, const std::vector& root_table); -void fft_inner_parallel(fr* coeffs, const evaluation_domain& domain, const fr&, const std::vector& root_table); +void fft_inner_serial(std::vector coeffs, const size_t domain_size, const std::vector& root_table); +void fft_inner_parallel(std::vector coeffs, + const evaluation_domain& domain, + const fr&, + const std::vector& root_table); void fft(fr* coeffs, const evaluation_domain& domain); +void fft(std::vector coeffs, const evaluation_domain& domain); void fft_with_constant(fr* coeffs, const evaluation_domain& domain, const fr& value); void coset_fft(fr* coeffs, const evaluation_domain& domain); +void coset_fft(std::vector coeffs, const evaluation_domain& domain); void coset_fft(fr* coeffs, const evaluation_domain& small_domain, const evaluation_domain& large_domain, @@ -33,10 +39,12 @@ void coset_fft_with_constant(fr* coeffs, const evaluation_domain& domain, const void coset_fft_with_generator_shift(fr* coeffs, const evaluation_domain& domain, const fr& constant); void ifft(fr* coeffs, const evaluation_domain& domain); +void ifft(std::vector coeffs, const evaluation_domain& domain); void ifft_with_constant(fr* coeffs, const evaluation_domain& domain, const fr& value); void coset_ifft(fr* coeffs, const evaluation_domain& domain); +void coset_ifft(std::vector coeffs, const evaluation_domain& domain); void add(const fr* a_coeffs, const fr* b_coeffs, fr* r_coeffs, const evaluation_domain& domain); void sub(const fr* a_coeffs, const fr* b_coeffs, fr* r_coeffs, const evaluation_domain& domain); @@ -53,7 +61,7 @@ void compute_lagrange_polynomial_fft(fr* l_1_coefficients, const evaluation_domain& src_domain, const evaluation_domain& target_domain); -void divide_by_pseudo_vanishing_polynomial(fr* coeffs, +void divide_by_pseudo_vanishing_polynomial(std::vector coeffs, const evaluation_domain& src_domain, const evaluation_domain& target_domain, const size_t num_roots_cut_out_of_vanishing_polynomial = 4); @@ -64,7 +72,9 @@ void divide_by_pseudo_vanishing_polynomial(fr* coeffs, fr compute_kate_opening_coefficients(const fr* src, fr* dest, const fr& z, const size_t n); // compute Z_H*(z), l_start(z), l_{end}(z) (= l_{n-4}(z)) -lagrange_evaluations get_lagrange_evaluations(const fr& z, const evaluation_domain& domain, const size_t num_roots_cut_out_of_vanishing_polynomial = 4); +lagrange_evaluations get_lagrange_evaluations(const fr& z, + const evaluation_domain& domain, + const size_t num_roots_cut_out_of_vanishing_polynomial = 4); fr compute_barycentric_evaluation(fr* coeffs, const size_t num_coeffs, const fr& z, const evaluation_domain& domain); // Convert an fft with `current_size` point evaluations, to one with `current_size >> compress_factor` point evaluations void compress_fft(const fr* src, fr* dest, const size_t current_size, const size_t compress_factor); diff --git a/src/aztec/polynomials/polynomial_arithmetic.test.cpp b/src/aztec/polynomials/polynomial_arithmetic.test.cpp index 84c6dbc0512..6298767f5c9 100644 --- a/src/aztec/polynomials/polynomial_arithmetic.test.cpp +++ b/src/aztec/polynomials/polynomial_arithmetic.test.cpp @@ -67,6 +67,65 @@ TEST(polynomials, fft_with_small_degree) } } +TEST(polynomials, split_polynomial_fft) +{ + size_t n = 256; + fr fft_transform[n]; + fr poly[n]; + + for (size_t i = 0; i < n; ++i) { + poly[i] = fr::random_element(); + fr::__copy(poly[i], fft_transform[i]); + } + + size_t num_poly = 4; + size_t n_poly = n / num_poly; + fr fft_transform_[num_poly][n_poly]; + for (size_t i = 0; i < n; ++i) { + fft_transform_[i / n_poly][i % n_poly] = poly[i]; + } + + evaluation_domain domain = evaluation_domain(n); + domain.compute_lookup_table(); + polynomial_arithmetic::fft(fft_transform, domain); + polynomial_arithmetic::fft({ fft_transform_[0], fft_transform_[1], fft_transform_[2], fft_transform_[3] }, domain); + + fr work_root; + work_root = fr::one(); + fr expected; + + for (size_t i = 0; i < n; ++i) { + expected = polynomial_arithmetic::evaluate(poly, work_root, n); + EXPECT_EQ((fft_transform[i] == expected), true); + EXPECT_EQ(fft_transform_[i / n_poly][i % n_poly], fft_transform[i]); + work_root *= domain.root; + } +} + +TEST(polynomials, split_polynomial_evaluate) +{ + size_t n = 256; + fr fft_transform[n]; + fr poly[n]; + + for (size_t i = 0; i < n; ++i) { + poly[i] = fr::random_element(); + fr::__copy(poly[i], fft_transform[i]); + } + + size_t num_poly = 4; + size_t n_poly = n / num_poly; + fr fft_transform_[num_poly][n_poly]; + for (size_t i = 0; i < n; ++i) { + fft_transform_[i / n_poly][i % n_poly] = poly[i]; + } + + fr z = fr::random_element(); + EXPECT_EQ(polynomial_arithmetic::evaluate( + { fft_transform_[0], fft_transform_[1], fft_transform_[2], fft_transform_[3] }, z, n), + polynomial_arithmetic::evaluate(poly, z, n)); +} + TEST(polynomials, basic_fft) { size_t n = 1 << 14; @@ -109,6 +168,36 @@ TEST(polynomials, fft_ifft_consistency) } } +TEST(polynomials, split_polynomial_fft_ifft_consistency) +{ + size_t n = 256; + size_t num_poly = 4; + fr result[num_poly][n]; + fr expected[num_poly][n]; + for (size_t j = 0; j < num_poly; j++) { + for (size_t i = 0; i < n; ++i) { + result[j][i] = fr::random_element(); + fr::__copy(result[j][i], expected[j][i]); + } + } + + evaluation_domain domain = evaluation_domain(num_poly * n); + domain.compute_lookup_table(); + + std::vector coeffs_vec; + for (size_t j = 0; j < num_poly; j++) { + coeffs_vec.push_back(result[j]); + } + polynomial_arithmetic::fft(coeffs_vec, domain); + polynomial_arithmetic::ifft(coeffs_vec, domain); + + for (size_t j = 0; j < num_poly; j++) { + for (size_t i = 0; i < n; ++i) { + EXPECT_EQ((result[j][i] == expected[j][i]), true); + } + } +} + TEST(polynomials, fft_coset_ifft_consistency) { size_t n = 256; @@ -133,6 +222,36 @@ TEST(polynomials, fft_coset_ifft_consistency) } } +TEST(polynomials, split_polynomial_fft_coset_ifft_consistency) +{ + size_t n = 256; + size_t num_poly = 4; + fr result[num_poly][n]; + fr expected[num_poly][n]; + for (size_t j = 0; j < num_poly; j++) { + for (size_t i = 0; i < n; ++i) { + result[j][i] = fr::random_element(); + fr::__copy(result[j][i], expected[j][i]); + } + } + + evaluation_domain domain = evaluation_domain(num_poly * n); + domain.compute_lookup_table(); + + std::vector coeffs_vec; + for (size_t j = 0; j < num_poly; j++) { + coeffs_vec.push_back(result[j]); + } + polynomial_arithmetic::coset_fft(coeffs_vec, domain); + polynomial_arithmetic::coset_ifft(coeffs_vec, domain); + + for (size_t j = 0; j < num_poly; j++) { + for (size_t i = 0; i < n; ++i) { + EXPECT_EQ((result[j][i] == expected[j][i]), true); + } + } +} + TEST(polynomials, fft_coset_ifft_cross_consistency) { size_t n = 2; @@ -259,29 +378,29 @@ TEST(polynomials, divide_by_pseudo_vanishing_polynomial) // make the final evaluation not vanish // c[n-1].one(); evaluation_domain small_domain = evaluation_domain(n); - evaluation_domain mid_domain = evaluation_domain(4 * n); + evaluation_domain large_domain = evaluation_domain(4 * n); small_domain.compute_lookup_table(); - mid_domain.compute_lookup_table(); + large_domain.compute_lookup_table(); polynomial_arithmetic::ifft(a, small_domain); polynomial_arithmetic::ifft(b, small_domain); polynomial_arithmetic::ifft(c, small_domain); - polynomial_arithmetic::coset_fft(a, mid_domain); - polynomial_arithmetic::coset_fft(b, mid_domain); - polynomial_arithmetic::coset_fft(c, mid_domain); + polynomial_arithmetic::coset_fft(a, large_domain); + polynomial_arithmetic::coset_fft(b, large_domain); + polynomial_arithmetic::coset_fft(c, large_domain); - fr result[mid_domain.size]; - for (size_t i = 0; i < mid_domain.size; ++i) { + fr result[large_domain.size]; + for (size_t i = 0; i < large_domain.size; ++i) { result[i] = a[i] * b[i]; result[i] += c[i]; } - polynomial_arithmetic::divide_by_pseudo_vanishing_polynomial(&result[0], small_domain, mid_domain, 1); + polynomial_arithmetic::divide_by_pseudo_vanishing_polynomial({ &result[0] }, small_domain, large_domain, 1); - polynomial_arithmetic::coset_ifft(result, mid_domain); + polynomial_arithmetic::coset_ifft(result, large_domain); - for (size_t i = n + 1; i < mid_domain.size; ++i) { + for (size_t i = n + 1; i < large_domain.size; ++i) { EXPECT_EQ((result[i] == fr::zero()), true); } @@ -408,76 +527,75 @@ TEST(polynomials, barycentric_weight_evaluations) EXPECT_EQ((result == expected), true); } +TEST(polynomials, divide_by_vanishing_polynomial) +{ + // generate mock polys A(X), B(X), C(X) + // A(X)B(X) - C(X) = 0 mod Z_H'(X) + // A(X)B(X) - C(X) = 0 mod Z_H(X) -TEST(polynomials, divide_by_vanishing_polynomial) -{ - // generate mock polys A(X), B(X), C(X) - // A(X)B(X) - C(X) = 0 mod Z_H'(X) - // A(X)B(X) - C(X) = 0 mod Z_H(X) - - constexpr size_t n = 16; + constexpr size_t n = 16; - polynomial A(n, 2 * n); - polynomial B(n, 2 * n); - polynomial C(n, 2 * n); + polynomial A(n, 2 * n); + polynomial B(n, 2 * n); + polynomial C(n, 2 * n); - for (size_t i = 0; i < 13; ++i) { - A[i] = fr::random_element(); - B[i] = fr::random_element(); - C[i] = A[i] * B[i]; - } - for (size_t i = 13; i < 16; ++i) { - A[i] = 1; - B[i] = 2; - C[i] = 3; - } + for (size_t i = 0; i < 13; ++i) { + A[i] = fr::random_element(); + B[i] = fr::random_element(); + C[i] = A[i] * B[i]; + } + for (size_t i = 13; i < 16; ++i) { + A[i] = 1; + B[i] = 2; + C[i] = 3; + } - evaluation_domain small_domain(n); - evaluation_domain large_domain(2 * n); + evaluation_domain small_domain(n); + evaluation_domain large_domain(2 * n); - small_domain.compute_lookup_table(); - large_domain.compute_lookup_table(); + small_domain.compute_lookup_table(); + large_domain.compute_lookup_table(); - A.ifft(small_domain); - B.ifft(small_domain); - C.ifft(small_domain); + A.ifft(small_domain); + B.ifft(small_domain); + C.ifft(small_domain); - fr z = fr::random_element(); - fr a_eval = A.evaluate(z, n); - fr b_eval = B.evaluate(z, n); - fr c_eval = C.evaluate(z, n); + fr z = fr::random_element(); + fr a_eval = A.evaluate(z, n); + fr b_eval = B.evaluate(z, n); + fr c_eval = C.evaluate(z, n); - A.coset_fft(large_domain); - B.coset_fft(large_domain); - C.coset_fft(large_domain); + A.coset_fft(large_domain); + B.coset_fft(large_domain); + C.coset_fft(large_domain); - // compute A(X) * B(X) - C(X) - polynomial R(2 * n, 2 * n); + // compute A(X) * B(X) - C(X) + polynomial R(2 * n, 2 * n); - polynomial_arithmetic::mul(&A[0], &B[0], &R[0], large_domain); - polynomial_arithmetic::sub(&R[0], &C[0], &R[0], large_domain); + polynomial_arithmetic::mul(&A[0], &B[0], &R[0], large_domain); + polynomial_arithmetic::sub(&R[0], &C[0], &R[0], large_domain); - polynomial R_copy(2 * n, 2 * n); - R_copy = R; - // polynomial R_copy(R); + polynomial R_copy(2 * n, 2 * n); + R_copy = R; + // polynomial R_copy(R); - polynomial_arithmetic::divide_by_pseudo_vanishing_polynomial(&R[0], small_domain, large_domain, 3); - R.coset_ifft(large_domain); + polynomial_arithmetic::divide_by_pseudo_vanishing_polynomial({ &R[0] }, small_domain, large_domain, 3); + R.coset_ifft(large_domain); - fr r_eval = R.evaluate(z, 2 * n); + fr r_eval = R.evaluate(z, 2 * n); - fr Z_H_eval = (z.pow(16) - 1) / ((z - small_domain.root_inverse) * (z - small_domain.root_inverse.sqr()) * - (z - small_domain.root_inverse * small_domain.root_inverse.sqr())); + fr Z_H_eval = (z.pow(16) - 1) / ((z - small_domain.root_inverse) * (z - small_domain.root_inverse.sqr()) * + (z - small_domain.root_inverse * small_domain.root_inverse.sqr())); - fr lhs = a_eval * b_eval - c_eval; - fr rhs = r_eval * Z_H_eval; - EXPECT_EQ(lhs, rhs); + fr lhs = a_eval * b_eval - c_eval; + fr rhs = r_eval * Z_H_eval; + EXPECT_EQ(lhs, rhs); - polynomial_arithmetic::divide_by_pseudo_vanishing_polynomial(&R_copy[0], small_domain, large_domain, 0); - R_copy.coset_ifft(large_domain); + polynomial_arithmetic::divide_by_pseudo_vanishing_polynomial({ &R_copy[0] }, small_domain, large_domain, 0); + R_copy.coset_ifft(large_domain); - r_eval = R_copy.evaluate(z, 2 * n); - fr Z_H_vanishing_eval = (z.pow(16) - 1); - rhs = r_eval * Z_H_vanishing_eval; - EXPECT_EQ((lhs == rhs), false); -} \ No newline at end of file + r_eval = R_copy.evaluate(z, 2 * n); + fr Z_H_vanishing_eval = (z.pow(16) - 1); + rhs = r_eval * Z_H_vanishing_eval; + EXPECT_EQ((lhs == rhs), false); +} \ No newline at end of file diff --git a/src/aztec/polynomials/polynomials.bench.cpp b/src/aztec/polynomials/polynomials.bench.cpp index 2dd4f193795..7f93be3a20a 100644 --- a/src/aztec/polynomials/polynomials.bench.cpp +++ b/src/aztec/polynomials/polynomials.bench.cpp @@ -241,7 +241,7 @@ void fft_bench_serial(State& state) noexcept for (auto _ : state) { size_t idx = (size_t)numeric::get_msb((uint64_t)state.range(0)) - (size_t)numeric::get_msb(START); barretenberg::polynomial_arithmetic::fft_inner_serial( - globals.data, evaluation_domains[idx].thread_size, evaluation_domains[idx].get_round_roots()); + { globals.data }, evaluation_domains[idx].thread_size, evaluation_domains[idx].get_round_roots()); } } BENCHMARK(fft_bench_serial)->RangeMultiplier(2)->Range(START * 4, MAX_GATES * 4); diff --git a/src/aztec/rollup/proofs/account/account.cpp b/src/aztec/rollup/proofs/account/account.cpp index 8fccc571bd3..694d2294fd9 100644 --- a/src/aztec/rollup/proofs/account/account.cpp +++ b/src/aztec/rollup/proofs/account/account.cpp @@ -204,7 +204,7 @@ void init_verification_key(std::shared_ptr const throw_or_abort("Compute proving key first."); } else { // Patch the 'nothing' reference string fed to init_proving_key. - proving_key->reference_string = crs_factory->get_prover_crs(proving_key->n); + proving_key->reference_string = crs_factory->get_prover_crs(proving_key->n + 1); } verification_key = waffle::turbo_composer::compute_verification_key(proving_key, crs_factory->get_verifier_crs()); } diff --git a/src/aztec/rollup/proofs/join_split/join_split.cpp b/src/aztec/rollup/proofs/join_split/join_split.cpp index fdd78a44a9f..46037a47d75 100644 --- a/src/aztec/rollup/proofs/join_split/join_split.cpp +++ b/src/aztec/rollup/proofs/join_split/join_split.cpp @@ -43,7 +43,7 @@ void init_verification_key(std::unique_ptr&& crs std::abort(); } // Patch the 'nothing' reference string fed to init_proving_key. - proving_key->reference_string = crs_factory->get_prover_crs(proving_key->n); + proving_key->reference_string = crs_factory->get_prover_crs(proving_key->n + 1); verification_key = waffle::turbo_composer::compute_verification_key(proving_key, crs_factory->get_verifier_crs()); } From 806e15539e03d3bcf28064a1619ff0c7c365ebb4 Mon Sep 17 00:00:00 2001 From: Zachary James Williamson Date: Mon, 8 Aug 2022 09:25:07 +0100 Subject: [PATCH 2/3] schnorr k parameter now derived from 512-bit int not 256-bit (#1018) * schnorr k parameter now derived from 512-bit int not 256-bit * replaced `get_field_from_byte_buffer` with `get_unbiased_field_from_hmac` Added HMAC to ecdsa Co-authored-by: Ariel * include fix Co-authored-by: Ariel Co-authored-by: arielgabizon --- src/aztec/crypto/ecdsa/ecdsa_impl.hpp | 8 +++++- src/aztec/crypto/hmac/hmac.hpp | 38 +++++++++++++++++++++++++++ src/aztec/crypto/schnorr/schnorr.tcc | 5 ++-- 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/aztec/crypto/ecdsa/ecdsa_impl.hpp b/src/aztec/crypto/ecdsa/ecdsa_impl.hpp index d9ac8710d47..9be623aeef3 100644 --- a/src/aztec/crypto/ecdsa/ecdsa_impl.hpp +++ b/src/aztec/crypto/ecdsa/ecdsa_impl.hpp @@ -2,6 +2,7 @@ #include #include +#include "../hmac/hmac.hpp" namespace crypto { namespace ecdsa { @@ -10,7 +11,12 @@ template signature construct_signature(const std::string& message, const key_pair& account) { signature sig; - Fr k = Fr::random_element(); // TODO replace with HMAC + + // use HMAC in PRF mode to derive 32-byte secret `k` + std::vector pkey_buffer; + write(pkey_buffer, account.private_key); + Fr k = crypto::get_unbiased_field_from_hmac(message, pkey_buffer); + typename G1::affine_element R(G1::one * k); Fq::serialize_to_buffer(R.x, &sig.r[0]); diff --git a/src/aztec/crypto/hmac/hmac.hpp b/src/aztec/crypto/hmac/hmac.hpp index efe0fbd3427..30b3a679415 100644 --- a/src/aztec/crypto/hmac/hmac.hpp +++ b/src/aztec/crypto/hmac/hmac.hpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace crypto { /** @@ -69,4 +70,41 @@ std::array hmac(const MessageContainer& message, con return result; } +/** + * @brief Takes a size-HASH_OUTPUT buffer from HMAC and converts into a field element + * + * @details We assume HASH_OUTPUT = 32, which is insufficient entropy. We hash input with `0` and `1` to produce 64 + * bytes of input data. This is then converted into a uin512_t, which is taken modulo Fr::modulus to produce our field + * element. + * + * @tparam Hash the hash function we're using + * @tparam Fr field type + * @param input the input buffer + * @return Fr output field element + */ +template +Fr get_unbiased_field_from_hmac(const MessageContainer& message, const KeyContainer& key) +{ + auto input = hmac(message, key); + + std::vector lo_buffer(input.begin(), input.end()); + lo_buffer.push_back(0); + std::vector hi_buffer(input.begin(), input.end()); + hi_buffer.push_back(1); + + auto klo = Hash::hash(lo_buffer); + auto khi = Hash::hash(hi_buffer); + + std::vector full_buffer(khi.begin(), khi.end()); + for (auto& v : klo) { + full_buffer.push_back(v); + } + + uint512_t field_as_u512; + const uint8_t* ptr = &full_buffer[0]; + numeric::read(ptr, field_as_u512); + + Fr result((field_as_u512 % Fr::modulus).lo); + return result; +} } // namespace crypto \ No newline at end of file diff --git a/src/aztec/crypto/schnorr/schnorr.tcc b/src/aztec/crypto/schnorr/schnorr.tcc index 1fec0334a3f..f5c69c5a527 100644 --- a/src/aztec/crypto/schnorr/schnorr.tcc +++ b/src/aztec/crypto/schnorr/schnorr.tcc @@ -6,6 +6,7 @@ #include #include + namespace crypto { namespace schnorr { @@ -33,9 +34,9 @@ signature construct_signature(const std::string& message, const key_pair // use HMAC in PRF mode to derive 32-byte secret `k` std::vector pkey_buffer; write(pkey_buffer, private_key); - std::array k_buffer = crypto::hmac(message, pkey_buffer); - Fr k = Fr::serialize_from_buffer(&k_buffer[0]); + Fr k = crypto::get_unbiased_field_from_hmac(message, pkey_buffer); + typename G1::affine_element R(G1::one * k); std::vector message_buffer; From 1d321feff41f9ebed7e84922c164ecd044cb2412 Mon Sep 17 00:00:00 2001 From: Innokentii Sennovskii Date: Mon, 8 Aug 2022 17:31:43 +0100 Subject: [PATCH 3/3] Fixed equality operator in affine_element (#1191) * Fixed equality operator in affine_element * Added regression test. * Fix typo in comment. Co-authored-by: codygunton --- src/aztec/ecc/groups/affine_element.test.cpp | 17 +++++++++++++++-- src/aztec/ecc/groups/affine_element_impl.hpp | 7 +++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/aztec/ecc/groups/affine_element.test.cpp b/src/aztec/ecc/groups/affine_element.test.cpp index 1b43f81f032..eecde30ac63 100644 --- a/src/aztec/ecc/groups/affine_element.test.cpp +++ b/src/aztec/ecc/groups/affine_element.test.cpp @@ -3,9 +3,11 @@ #include #include +namespace test_affine_element { + using namespace barretenberg; -TEST(AffineElement, ReadWriteBuffer) +TEST(affine_element, read_write_buffer) { g1::affine_element P = g1::affine_element(g1::element::random_element()); g1::affine_element Q; @@ -22,4 +24,15 @@ TEST(AffineElement, ReadWriteBuffer) ASSERT_FALSE(P == Q); ASSERT_TRUE(P == R); -} \ No newline at end of file +} + +// Regression test to ensure that the point at infinity is not equal to its coordinate-wise reduction, which may lie +// on the curve, depending on the y-coordinate. +TEST(affine_element, infinity_regression) +{ + g1::affine_element P; + P.self_set_infinity(); + g1::affine_element R(0, P.y); + ASSERT_FALSE(P == R); +} +} // namespace test_affine_element \ No newline at end of file diff --git a/src/aztec/ecc/groups/affine_element_impl.hpp b/src/aztec/ecc/groups/affine_element_impl.hpp index 3a064d7e9c4..b6b6f622495 100644 --- a/src/aztec/ecc/groups/affine_element_impl.hpp +++ b/src/aztec/ecc/groups/affine_element_impl.hpp @@ -123,8 +123,11 @@ template constexpr bool affine_element: template constexpr bool affine_element::operator==(const affine_element& other) const noexcept { - bool both_infinity = is_point_at_infinity() && other.is_point_at_infinity(); - return both_infinity || ((x == other.x) && (y == other.y)); + bool this_is_infinity = is_point_at_infinity(); + bool other_is_infinity = other.is_point_at_infinity(); + bool both_infinity = this_is_infinity && other_is_infinity; + bool only_one_is_infinity = this_is_infinity != other_is_infinity; + return !only_one_is_infinity && (both_infinity || ((x == other.x) && (y == other.y))); } /**