diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/bn254.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/bn254.hpp index 84688be1a62..dadc92964b0 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/bn254.hpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/bn254.hpp @@ -17,5 +17,10 @@ class BN254 { using G2AffineElement = typename barretenberg::g2::affine_element; using G2BaseField = typename barretenberg::fq2; using TargetField = barretenberg::fq12; + + // TODO(#673): This flag is temporary. It is needed in the verifier classes (GeminiVerifier, etc.) while these + // classes are instantiated with "native" curve types. Eventually, the verifier classes will be instantiated only + // with stdlib types, and "native" verification will be acheived via a simulated builder. + static constexpr bool is_stdlib_type = false; }; } // namespace curve \ No newline at end of file diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/grumpkin.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/grumpkin.hpp index fc799acce55..9d654ec5695 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/grumpkin.hpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/grumpkin.hpp @@ -42,5 +42,10 @@ class Grumpkin { using Group = typename grumpkin::g1; using Element = typename Group::element; using AffineElement = typename Group::affine_element; + + // TODO(#673): This flag is temporary. It is needed in the verifier classes (GeminiVerifier, etc.) while these + // classes are instantiated with "native" curve types. Eventually, the verifier classes will be instantiated only + // with stdlib types, and "native" verification will be acheived via a simulated builder. + static constexpr bool is_stdlib_type = false; }; } // namespace curve \ No newline at end of file diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/flavor/ultra_recursive.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/flavor/ultra_recursive.hpp new file mode 100644 index 00000000000..3162b860482 --- /dev/null +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/flavor/ultra_recursive.hpp @@ -0,0 +1,396 @@ +#pragma once +#include "barretenberg/ecc/curves/bn254/g1.hpp" +#include "barretenberg/honk/pcs/commitment_key.hpp" +#include "barretenberg/honk/pcs/kzg/kzg.hpp" +#include "barretenberg/honk/sumcheck/polynomials/barycentric_data.hpp" +#include "barretenberg/honk/sumcheck/polynomials/univariate.hpp" +#include "barretenberg/honk/sumcheck/relations/auxiliary_relation.hpp" +#include "barretenberg/honk/sumcheck/relations/elliptic_relation.hpp" +#include "barretenberg/honk/sumcheck/relations/gen_perm_sort_relation.hpp" +#include "barretenberg/honk/sumcheck/relations/lookup_relation.hpp" +#include "barretenberg/honk/sumcheck/relations/permutation_relation.hpp" +#include "barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation.hpp" +#include "barretenberg/honk/transcript/transcript.hpp" +#include "barretenberg/polynomials/evaluation_domain.hpp" +#include "barretenberg/polynomials/polynomial.hpp" +#include "barretenberg/proof_system/circuit_builder/ultra_circuit_builder.hpp" +#include "barretenberg/proof_system/flavor/flavor.hpp" +#include "barretenberg/srs/factories/crs_factory.hpp" +#include +#include +#include +#include +#include +#include + +#include "barretenberg/stdlib/primitives/curves/bn254.hpp" +#include "barretenberg/stdlib/primitives/field/field.hpp" + +namespace proof_system::honk::flavor { + +/** + * @brief The recursive counterpart to the "native" Ultra flavor. + * @details This flavor can be used to instantiate a recursive Ultra Honk verifier for a proof created using the + * conventional Ultra flavor. It is similar in structure to its native counterpart with two main differences: 1) the + * curve types are stdlib types (e.g. field_t instead of field) and 2) it does not specify any Prover related types + * (e.g. Polynomial, ExtendedEdges, etc.) since we do not emulate prover computation in circuits, i.e. it only makes + * sense to instantiate a Verifier with this flavor. + * + */ +class UltraRecursive { + public: + using CircuitBuilder = UltraCircuitBuilder; + using Curve = plonk::stdlib::bn254; + using PCS = pcs::kzg::KZG; + using GroupElement = Curve::Element; + using Commitment = Curve::Element; + using CommitmentHandle = Curve::Element; + using FF = Curve::ScalarField; + + // Note(luke): Eventually this may not be needed at all + using VerifierCommitmentKey = pcs::VerifierCommitmentKey; + + static constexpr size_t NUM_WIRES = CircuitBuilder::NUM_WIRES; + // The number of multivariate polynomials on which a sumcheck prover sumcheck operates (including shifts). We often + // need containers of this size to hold related data, so we choose a name more agnostic than `NUM_POLYNOMIALS`. + // Note: this number does not include the individual sorted list polynomials. + static constexpr size_t NUM_ALL_ENTITIES = 43; + // The number of polynomials precomputed to describe a circuit and to aid a prover in constructing a satisfying + // assignment of witnesses. We again choose a neutral name. + static constexpr size_t NUM_PRECOMPUTED_ENTITIES = 25; + // The total number of witness entities not including shifts. + static constexpr size_t NUM_WITNESS_ENTITIES = 11; + + // define the tuple of Relations that comprise the Sumcheck relation + using Relations = std::tuple, + sumcheck::UltraPermutationRelation, + sumcheck::LookupRelation, + sumcheck::GenPermSortRelation, + sumcheck::EllipticRelation, + sumcheck::AuxiliaryRelation>; + + static constexpr size_t MAX_RELATION_LENGTH = get_max_relation_length(); + + // MAX_RANDOM_RELATION_LENGTH = algebraic degree of sumcheck relation *after* multiplying by the `pow_zeta` random + // polynomial e.g. For \sum(x) [A(x) * B(x) + C(x)] * PowZeta(X), relation length = 2 and random relation length = 3 + static constexpr size_t MAX_RANDOM_RELATION_LENGTH = MAX_RELATION_LENGTH + 1; + static constexpr size_t NUM_RELATIONS = std::tuple_size::value; + + // define the container for storing the univariate contribution from each relation in Sumcheck + using RelationUnivariates = decltype(create_relation_univariates_container()); + using RelationValues = decltype(create_relation_values_container()); + + private: + template + /** + * @brief A base class labelling precomputed entities and (ordered) subsets of interest. + * @details Used to build the proving key and verification key. + */ + class PrecomputedEntities : public PrecomputedEntities_ { + public: + DataType& q_m = std::get<0>(this->_data); + DataType& q_c = std::get<1>(this->_data); + DataType& q_l = std::get<2>(this->_data); + DataType& q_r = std::get<3>(this->_data); + DataType& q_o = std::get<4>(this->_data); + DataType& q_4 = std::get<5>(this->_data); + DataType& q_arith = std::get<6>(this->_data); + DataType& q_sort = std::get<7>(this->_data); + DataType& q_elliptic = std::get<8>(this->_data); + DataType& q_aux = std::get<9>(this->_data); + DataType& q_lookup = std::get<10>(this->_data); + DataType& sigma_1 = std::get<11>(this->_data); + DataType& sigma_2 = std::get<12>(this->_data); + DataType& sigma_3 = std::get<13>(this->_data); + DataType& sigma_4 = std::get<14>(this->_data); + DataType& id_1 = std::get<15>(this->_data); + DataType& id_2 = std::get<16>(this->_data); + DataType& id_3 = std::get<17>(this->_data); + DataType& id_4 = std::get<18>(this->_data); + DataType& table_1 = std::get<19>(this->_data); + DataType& table_2 = std::get<20>(this->_data); + DataType& table_3 = std::get<21>(this->_data); + DataType& table_4 = std::get<22>(this->_data); + DataType& lagrange_first = std::get<23>(this->_data); + DataType& lagrange_last = std::get<24>(this->_data); + + static constexpr CircuitType CIRCUIT_TYPE = CircuitBuilder::CIRCUIT_TYPE; + + std::vector get_selectors() override + { + return { q_m, q_c, q_l, q_r, q_o, q_4, q_arith, q_sort, q_elliptic, q_aux, q_lookup }; + }; + std::vector get_sigma_polynomials() override { return { sigma_1, sigma_2, sigma_3, sigma_4 }; }; + std::vector get_id_polynomials() override { return { id_1, id_2, id_3, id_4 }; }; + + std::vector get_table_polynomials() { return { table_1, table_2, table_3, table_4 }; }; + }; + + /** + * @brief Container for all witness polynomials used/constructed by the prover. + * @details Shifts are not included here since they do not occupy their own memory. + */ + template + class WitnessEntities : public WitnessEntities_ { + public: + DataType& w_l = std::get<0>(this->_data); + DataType& w_r = std::get<1>(this->_data); + DataType& w_o = std::get<2>(this->_data); + DataType& w_4 = std::get<3>(this->_data); + DataType& sorted_1 = std::get<4>(this->_data); + DataType& sorted_2 = std::get<5>(this->_data); + DataType& sorted_3 = std::get<6>(this->_data); + DataType& sorted_4 = std::get<7>(this->_data); + DataType& sorted_accum = std::get<8>(this->_data); + DataType& z_perm = std::get<9>(this->_data); + DataType& z_lookup = std::get<10>(this->_data); + + std::vector get_wires() override { return { w_l, w_r, w_o, w_4 }; }; + // The sorted concatenations of table and witness data needed for plookup. + std::vector get_sorted_polynomials() { return { sorted_1, sorted_2, sorted_3, sorted_4 }; }; + }; + + /** + * @brief A base class labelling all entities (for instance, all of the polynomials used by the prover during + * sumcheck) in this Honk variant along with particular subsets of interest + * @details Used to build containers for: the prover's polynomial during sumcheck; the sumcheck's folded + * polynomials; the univariates consturcted during during sumcheck; the evaluations produced by sumcheck. + * + * Symbolically we have: AllEntities = PrecomputedEntities + WitnessEntities + "ShiftedEntities". It could be + * implemented as such, but we have this now. + */ + template + class AllEntities : public AllEntities_ { + public: + DataType& q_c = std::get<0>(this->_data); + DataType& q_l = std::get<1>(this->_data); + DataType& q_r = std::get<2>(this->_data); + DataType& q_o = std::get<3>(this->_data); + DataType& q_4 = std::get<4>(this->_data); + DataType& q_m = std::get<5>(this->_data); + DataType& q_arith = std::get<6>(this->_data); + DataType& q_sort = std::get<7>(this->_data); + DataType& q_elliptic = std::get<8>(this->_data); + DataType& q_aux = std::get<9>(this->_data); + DataType& q_lookup = std::get<10>(this->_data); + DataType& sigma_1 = std::get<11>(this->_data); + DataType& sigma_2 = std::get<12>(this->_data); + DataType& sigma_3 = std::get<13>(this->_data); + DataType& sigma_4 = std::get<14>(this->_data); + DataType& id_1 = std::get<15>(this->_data); + DataType& id_2 = std::get<16>(this->_data); + DataType& id_3 = std::get<17>(this->_data); + DataType& id_4 = std::get<18>(this->_data); + DataType& table_1 = std::get<19>(this->_data); + DataType& table_2 = std::get<20>(this->_data); + DataType& table_3 = std::get<21>(this->_data); + DataType& table_4 = std::get<22>(this->_data); + DataType& lagrange_first = std::get<23>(this->_data); + DataType& lagrange_last = std::get<24>(this->_data); + DataType& w_l = std::get<25>(this->_data); + DataType& w_r = std::get<26>(this->_data); + DataType& w_o = std::get<27>(this->_data); + DataType& w_4 = std::get<28>(this->_data); + DataType& sorted_accum = std::get<29>(this->_data); + DataType& z_perm = std::get<30>(this->_data); + DataType& z_lookup = std::get<31>(this->_data); + DataType& table_1_shift = std::get<32>(this->_data); + DataType& table_2_shift = std::get<33>(this->_data); + DataType& table_3_shift = std::get<34>(this->_data); + DataType& table_4_shift = std::get<35>(this->_data); + DataType& w_l_shift = std::get<36>(this->_data); + DataType& w_r_shift = std::get<37>(this->_data); + DataType& w_o_shift = std::get<38>(this->_data); + DataType& w_4_shift = std::get<39>(this->_data); + DataType& sorted_accum_shift = std::get<40>(this->_data); + DataType& z_perm_shift = std::get<41>(this->_data); + DataType& z_lookup_shift = std::get<42>(this->_data); + + std::vector get_wires() override { return { w_l, w_r, w_o, w_4 }; }; + // Gemini-specific getters. + std::vector get_unshifted() override + { + return { q_c, q_l, q_r, q_o, q_4, q_m, q_arith, q_sort, + q_elliptic, q_aux, q_lookup, sigma_1, sigma_2, sigma_3, sigma_4, id_1, + id_2, id_3, id_4, table_1, table_2, table_3, table_4, lagrange_first, + lagrange_last, w_l, w_r, w_o, w_4, sorted_accum, z_perm, z_lookup + + }; + }; + std::vector get_to_be_shifted() override + { + return { table_1, table_2, table_3, table_4, w_l, w_r, w_o, w_4, sorted_accum, z_perm, z_lookup }; + }; + std::vector get_shifted() override + { + return { table_1_shift, table_2_shift, table_3_shift, table_4_shift, w_l_shift, w_r_shift, + w_o_shift, w_4_shift, sorted_accum_shift, z_perm_shift, z_lookup_shift }; + }; + + AllEntities() = default; + + AllEntities(const AllEntities& other) + : AllEntities_(other){}; + + AllEntities(AllEntities&& other) + : AllEntities_(other){}; + + AllEntities& operator=(const AllEntities& other) + { + if (this == &other) { + return *this; + } + AllEntities_::operator=(other); + return *this; + } + + AllEntities& operator=(AllEntities&& other) + { + AllEntities_::operator=(other); + return *this; + } + + ~AllEntities() = default; + }; + + public: + /** + * @brief The verification key is responsible for storing the the commitments to the precomputed (non-witnessk) + * polynomials used by the verifier. + * + * @note Note the discrepancy with what sort of data is stored here vs in the proving key. We may want to resolve + * that, and split out separate PrecomputedPolynomials/Commitments data for clarity but also for portability of our + * circuits. + */ + class VerificationKey : public VerificationKey_> { + public: + /** + * @brief Construct a new Verification Key with stdlib types from a provided native verification key + * + * @param builder + * @param native_key Native verification key from which to extract the precomputed commitments + */ + VerificationKey(CircuitBuilder* builder, auto native_key) + : VerificationKey_>(native_key->circuit_size, + native_key->num_public_inputs) + { + q_m = Commitment::from_witness(builder, native_key->q_m); + q_l = Commitment::from_witness(builder, native_key->q_l); + q_r = Commitment::from_witness(builder, native_key->q_r); + q_o = Commitment::from_witness(builder, native_key->q_o); + q_4 = Commitment::from_witness(builder, native_key->q_4); + q_c = Commitment::from_witness(builder, native_key->q_c); + q_arith = Commitment::from_witness(builder, native_key->q_arith); + q_sort = Commitment::from_witness(builder, native_key->q_sort); + q_elliptic = Commitment::from_witness(builder, native_key->q_elliptic); + q_aux = Commitment::from_witness(builder, native_key->q_aux); + q_lookup = Commitment::from_witness(builder, native_key->q_lookup); + sigma_1 = Commitment::from_witness(builder, native_key->sigma_1); + sigma_2 = Commitment::from_witness(builder, native_key->sigma_2); + sigma_3 = Commitment::from_witness(builder, native_key->sigma_3); + sigma_4 = Commitment::from_witness(builder, native_key->sigma_4); + id_1 = Commitment::from_witness(builder, native_key->id_1); + id_2 = Commitment::from_witness(builder, native_key->id_2); + id_3 = Commitment::from_witness(builder, native_key->id_3); + id_4 = Commitment::from_witness(builder, native_key->id_4); + table_1 = Commitment::from_witness(builder, native_key->table_1); + table_2 = Commitment::from_witness(builder, native_key->table_2); + table_3 = Commitment::from_witness(builder, native_key->table_3); + table_4 = Commitment::from_witness(builder, native_key->table_4); + lagrange_first = Commitment::from_witness(builder, native_key->lagrange_first); + lagrange_last = Commitment::from_witness(builder, native_key->lagrange_last); + }; + }; + + /** + * @brief A container for the polynomials evaluations produced during sumcheck, which are purported to be the + * evaluations of polynomials committed in earlier rounds. + */ + class ClaimedEvaluations : public AllEntities { + public: + using Base = AllEntities; + using Base::Base; + ClaimedEvaluations(std::array _data_in) { this->_data = _data_in; } + }; + + /** + * @brief A container for commitment labels. + * @note It's debatable whether this should inherit from AllEntities. since most entries are not strictly needed. It + * has, however, been useful during debugging to have these labels available. + * + */ + class CommitmentLabels : public AllEntities { + public: + CommitmentLabels() + { + w_l = "W_L"; + w_r = "W_R"; + w_o = "W_O"; + w_4 = "W_4"; + z_perm = "Z_PERM"; + z_lookup = "Z_LOOKUP"; + sorted_accum = "SORTED_ACCUM"; + + // The ones beginning with "__" are only used for debugging + q_c = "__Q_C"; + q_l = "__Q_L"; + q_r = "__Q_R"; + q_o = "__Q_O"; + q_4 = "__Q_4"; + q_m = "__Q_M"; + q_arith = "__Q_ARITH"; + q_sort = "__Q_SORT"; + q_elliptic = "__Q_ELLIPTIC"; + q_aux = "__Q_AUX"; + q_lookup = "__Q_LOOKUP"; + sigma_1 = "__SIGMA_1"; + sigma_2 = "__SIGMA_2"; + sigma_3 = "__SIGMA_3"; + sigma_4 = "__SIGMA_4"; + id_1 = "__ID_1"; + id_2 = "__ID_2"; + id_3 = "__ID_3"; + id_4 = "__ID_4"; + table_1 = "__TABLE_1"; + table_2 = "__TABLE_2"; + table_3 = "__TABLE_3"; + table_4 = "__TABLE_4"; + lagrange_first = "__LAGRANGE_FIRST"; + lagrange_last = "__LAGRANGE_LAST"; + }; + }; + + class VerifierCommitments : public AllEntities { + public: + VerifierCommitments(std::shared_ptr verification_key) + { + q_m = verification_key->q_m; + q_l = verification_key->q_l; + q_r = verification_key->q_r; + q_o = verification_key->q_o; + q_4 = verification_key->q_4; + q_c = verification_key->q_c; + q_arith = verification_key->q_arith; + q_sort = verification_key->q_sort; + q_elliptic = verification_key->q_elliptic; + q_aux = verification_key->q_aux; + q_lookup = verification_key->q_lookup; + sigma_1 = verification_key->sigma_1; + sigma_2 = verification_key->sigma_2; + sigma_3 = verification_key->sigma_3; + sigma_4 = verification_key->sigma_4; + id_1 = verification_key->id_1; + id_2 = verification_key->id_2; + id_3 = verification_key->id_3; + id_4 = verification_key->id_4; + table_1 = verification_key->table_1; + table_2 = verification_key->table_2; + table_3 = verification_key->table_3; + table_4 = verification_key->table_4; + lagrange_first = verification_key->lagrange_first; + lagrange_last = verification_key->lagrange_last; + } + }; +}; + +} // namespace proof_system::honk::flavor diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/pcs/gemini/gemini.cpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/pcs/gemini/gemini.cpp index fe2b0dbde86..3f5bf74fc5f 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/pcs/gemini/gemini.cpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/pcs/gemini/gemini.cpp @@ -186,136 +186,7 @@ ProverOutput GeminiProver_::compute_fold_polynomial_evaluations(st return { fold_poly_opening_pairs, std::move(fold_polynomials) }; }; -/** - * @brief Returns univariate opening claims for the Fold polynomials to be checked later - * - * @param mle_opening_point the MLE evaluation point u - * @param batched_evaluation batched evaluation from multivariate evals at the point u - * @param batched_f batched commitment to unshifted polynomials - * @param batched_g batched commitment to to-be-shifted polynomials - * @param proof commitments to the m-1 folded polynomials, and alleged evaluations. - * @param transcript - * @return Fold polynomial opening claims: (r, A₀(r), C₀₊), (-r, A₀(-r), C₀₋), and - * (Cⱼ, Aⱼ(-r^{2ʲ}), -r^{2}), j = [1, ..., m-1] - */ - -template -std::vector> GeminiVerifier_::reduce_verification(std::span mle_opening_point, /* u */ - const Fr batched_evaluation, /* all */ - GroupElement& batched_f, /* unshifted */ - GroupElement& batched_g, /* to-be-shifted */ - VerifierTranscript& transcript) -{ - const size_t num_variables = mle_opening_point.size(); - - // Get polynomials Fold_i, i = 1,...,m-1 from transcript - std::vector commitments; - commitments.reserve(num_variables - 1); - for (size_t i = 0; i < num_variables - 1; ++i) { - auto commitment = transcript.template receive_from_prover("Gemini:FOLD_" + std::to_string(i + 1)); - commitments.emplace_back(commitment); - } - - // compute vector of powers of random evaluation point r - const Fr r = transcript.get_challenge("Gemini:r"); - std::vector r_squares = squares_of_r(r, num_variables); - - // Get evaluations a_i, i = 0,...,m-1 from transcript - std::vector evaluations; - evaluations.reserve(num_variables); - for (size_t i = 0; i < num_variables; ++i) { - auto eval = transcript.template receive_from_prover("Gemini:a_" + std::to_string(i)); - evaluations.emplace_back(eval); - } - - // Compute evaluation A₀(r) - auto a_0_pos = compute_eval_pos(batched_evaluation, mle_opening_point, r_squares, evaluations); - - // C₀_r_pos = ∑ⱼ ρʲ⋅[fⱼ] + r⁻¹⋅∑ⱼ ρᵏ⁺ʲ [gⱼ] - // C₀_r_pos = ∑ⱼ ρʲ⋅[fⱼ] - r⁻¹⋅∑ⱼ ρᵏ⁺ʲ [gⱼ] - auto [c0_r_pos, c0_r_neg] = compute_simulated_commitments(batched_f, batched_g, r); - - std::vector> fold_polynomial_opening_claims; - fold_polynomial_opening_claims.reserve(num_variables + 1); - - // ( [A₀₊], r, A₀(r) ) - fold_polynomial_opening_claims.emplace_back(OpeningClaim{ { r, a_0_pos }, c0_r_pos }); - // ( [A₀₋], -r, A₀(-r) ) - fold_polynomial_opening_claims.emplace_back(OpeningClaim{ { -r, evaluations[0] }, c0_r_neg }); - for (size_t l = 0; l < num_variables - 1; ++l) { - // ([A₀₋], −r^{2ˡ}, Aₗ(−r^{2ˡ}) ) - fold_polynomial_opening_claims.emplace_back( - OpeningClaim{ { -r_squares[l + 1], evaluations[l + 1] }, commitments[l] }); - } - - return fold_polynomial_opening_claims; -}; - -/** - * @brief Compute the expected evaluation of the univariate commitment to the batched polynomial. - * - * @param batched_mle_eval The evaluation of the folded polynomials - * @param mle_vars MLE opening point u - * @param r_squares squares of r, r², ..., r^{2ᵐ⁻¹} - * @param fold_polynomial_evals series of Aᵢ₋₁(−r^{2ⁱ⁻¹}) - * @return evaluation A₀(r) - */ -template -typename Curve::ScalarField GeminiVerifier_::compute_eval_pos(const Fr batched_mle_eval, - std::span mle_vars, - std::span r_squares, - std::span fold_polynomial_evals) -{ - const size_t num_variables = mle_vars.size(); - - const auto& evals = fold_polynomial_evals; - - // Initialize eval_pos with batched MLE eval v = ∑ⱼ ρʲ vⱼ + ∑ⱼ ρᵏ⁺ʲ v↺ⱼ - Fr eval_pos = batched_mle_eval; - for (size_t l = num_variables; l != 0; --l) { - const Fr r = r_squares[l - 1]; // = rₗ₋₁ = r^{2ˡ⁻¹} - const Fr eval_neg = evals[l - 1]; // = Aₗ₋₁(−r^{2ˡ⁻¹}) - const Fr u = mle_vars[l - 1]; // = uₗ₋₁ - - // The folding property ensures that - // Aₗ₋₁(r^{2ˡ⁻¹}) + Aₗ₋₁(−r^{2ˡ⁻¹}) Aₗ₋₁(r^{2ˡ⁻¹}) - Aₗ₋₁(−r^{2ˡ⁻¹}) - // Aₗ(r^{2ˡ}) = (1-uₗ₋₁) ----------------------------- + uₗ₋₁ ----------------------------- - // 2 2r^{2ˡ⁻¹} - // We solve the above equation in Aₗ₋₁(r^{2ˡ⁻¹}), using the previously computed Aₗ(r^{2ˡ}) in eval_pos - // and using Aₗ₋₁(−r^{2ˡ⁻¹}) sent by the prover in the proof. - eval_pos = ((r * eval_pos * 2) - eval_neg * (r * (Fr(1) - u) - u)) / (r * (Fr(1) - u) + u); - } - - return eval_pos; // return A₀(r) -}; - -/** - * @brief Computes two commitments to A₀ partially evaluated in r and -r. - * - * @param batched_f batched commitment to non-shifted polynomials - * @param batched_g batched commitment to to-be-shifted polynomials - * @param r evaluation point at which we have partially evaluated A₀ at r and -r. - * @return std::pair c0_r_pos, c0_r_neg - */ -template -std::pair GeminiVerifier_::compute_simulated_commitments( - GroupElement& batched_f, GroupElement& batched_g, Fr r) -{ - // C₀ᵣ₊ = [F] + r⁻¹⋅[G] - GroupElement C0_r_pos = batched_f; - // C₀ᵣ₋ = [F] - r⁻¹⋅[G] - GroupElement C0_r_neg = batched_f; - Fr r_inv = r.invert(); - if (!batched_g.is_point_at_infinity()) { - batched_g *= r_inv; - C0_r_pos += batched_g; - C0_r_neg -= batched_g; - } - return { C0_r_pos, C0_r_neg }; -}; template class GeminiProver_; template class GeminiProver_; -template class GeminiVerifier_; -template class GeminiVerifier_; }; // namespace proof_system::honk::pcs::gemini diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/pcs/gemini/gemini.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/pcs/gemini/gemini.hpp index 27ab3969762..1fdbeb06353 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/pcs/gemini/gemini.hpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/pcs/gemini/gemini.hpp @@ -64,11 +64,11 @@ template struct ProverOutput { /** * @brief Compute powers of challenge ρ - * - * @tparam Fr - * @param rho - * @param num_powers - * @return std::vector + * + * @tparam Fr + * @param rho + * @param num_powers + * @return std::vector */ template inline std::vector powers_of_rho(const Fr rho, const size_t num_powers) { @@ -107,8 +107,8 @@ template class GeminiProver_ { Polynomial&& batched_to_be_shifted); static ProverOutput compute_fold_polynomial_evaluations(std::span mle_opening_point, - std::vector&& fold_polynomials, - const Fr& r_challenge); + std::vector&& fold_polynomials, + const Fr& r_challenge); }; // namespace proof_system::honk::pcs::gemini template class GeminiVerifier_ { @@ -117,26 +117,143 @@ template class GeminiVerifier_ { using Commitment = typename Curve::AffineElement; public: + /** + * @brief Returns univariate opening claims for the Fold polynomials to be checked later + * + * @param mle_opening_point the MLE evaluation point u + * @param batched_evaluation batched evaluation from multivariate evals at the point u + * @param batched_f batched commitment to unshifted polynomials + * @param batched_g batched commitment to to-be-shifted polynomials + * @param proof commitments to the m-1 folded polynomials, and alleged evaluations. + * @param transcript + * @return Fold polynomial opening claims: (r, A₀(r), C₀₊), (-r, A₀(-r), C₀₋), and + * (Cⱼ, Aⱼ(-r^{2ʲ}), -r^{2}), j = [1, ..., m-1] + */ static std::vector> reduce_verification(std::span mle_opening_point, /* u */ - const Fr batched_evaluation, /* all */ - GroupElement& batched_f, /* unshifted */ - GroupElement& batched_g, /* to-be-shifted */ - VerifierTranscript& transcript); + const Fr batched_evaluation, /* all */ + GroupElement& batched_f, /* unshifted */ + GroupElement& batched_g, /* to-be-shifted */ + auto& transcript) + { + const size_t num_variables = mle_opening_point.size(); + + // Get polynomials Fold_i, i = 1,...,m-1 from transcript + std::vector commitments; + commitments.reserve(num_variables - 1); + for (size_t i = 0; i < num_variables - 1; ++i) { + auto commitment = + transcript.template receive_from_prover("Gemini:FOLD_" + std::to_string(i + 1)); + commitments.emplace_back(commitment); + } + + // compute vector of powers of random evaluation point r + const Fr r = transcript.get_challenge("Gemini:r"); + std::vector r_squares = squares_of_r(r, num_variables); + + // Get evaluations a_i, i = 0,...,m-1 from transcript + std::vector evaluations; + evaluations.reserve(num_variables); + for (size_t i = 0; i < num_variables; ++i) { + auto eval = transcript.template receive_from_prover("Gemini:a_" + std::to_string(i)); + evaluations.emplace_back(eval); + } + + // Compute evaluation A₀(r) + auto a_0_pos = compute_eval_pos(batched_evaluation, mle_opening_point, r_squares, evaluations); + + // C₀_r_pos = ∑ⱼ ρʲ⋅[fⱼ] + r⁻¹⋅∑ⱼ ρᵏ⁺ʲ [gⱼ] + // C₀_r_pos = ∑ⱼ ρʲ⋅[fⱼ] - r⁻¹⋅∑ⱼ ρᵏ⁺ʲ [gⱼ] + auto [c0_r_pos, c0_r_neg] = compute_simulated_commitments(batched_f, batched_g, r); + + std::vector> fold_polynomial_opening_claims; + fold_polynomial_opening_claims.reserve(num_variables + 1); + + // ( [A₀₊], r, A₀(r) ) + fold_polynomial_opening_claims.emplace_back(OpeningClaim{ { r, a_0_pos }, c0_r_pos }); + // ( [A₀₋], -r, A₀(-r) ) + fold_polynomial_opening_claims.emplace_back(OpeningClaim{ { -r, evaluations[0] }, c0_r_neg }); + for (size_t l = 0; l < num_variables - 1; ++l) { + // ([A₀₋], −r^{2ˡ}, Aₗ(−r^{2ˡ}) ) + fold_polynomial_opening_claims.emplace_back( + OpeningClaim{ { -r_squares[l + 1], evaluations[l + 1] }, commitments[l] }); + } + + return fold_polynomial_opening_claims; + } private: + /** + * @brief Compute the expected evaluation of the univariate commitment to the batched polynomial. + * + * @param batched_mle_eval The evaluation of the folded polynomials + * @param mle_vars MLE opening point u + * @param r_squares squares of r, r², ..., r^{2ᵐ⁻¹} + * @param fold_polynomial_evals series of Aᵢ₋₁(−r^{2ⁱ⁻¹}) + * @return evaluation A₀(r) + */ static Fr compute_eval_pos(const Fr batched_mle_eval, std::span mle_vars, std::span r_squares, - std::span fold_polynomial_evals); + std::span fold_polynomial_evals) + { + const size_t num_variables = mle_vars.size(); + const auto& evals = fold_polynomial_evals; + + // Initialize eval_pos with batched MLE eval v = ∑ⱼ ρʲ vⱼ + ∑ⱼ ρᵏ⁺ʲ v↺ⱼ + Fr eval_pos = batched_mle_eval; + for (size_t l = num_variables; l != 0; --l) { + const Fr r = r_squares[l - 1]; // = rₗ₋₁ = r^{2ˡ⁻¹} + const Fr eval_neg = evals[l - 1]; // = Aₗ₋₁(−r^{2ˡ⁻¹}) + const Fr u = mle_vars[l - 1]; // = uₗ₋₁ + + // The folding property ensures that + // Aₗ₋₁(r^{2ˡ⁻¹}) + Aₗ₋₁(−r^{2ˡ⁻¹}) Aₗ₋₁(r^{2ˡ⁻¹}) - Aₗ₋₁(−r^{2ˡ⁻¹}) + // Aₗ(r^{2ˡ}) = (1-uₗ₋₁) ----------------------------- + uₗ₋₁ ----------------------------- + // 2 2r^{2ˡ⁻¹} + // We solve the above equation in Aₗ₋₁(r^{2ˡ⁻¹}), using the previously computed Aₗ(r^{2ˡ}) in eval_pos + // and using Aₗ₋₁(−r^{2ˡ⁻¹}) sent by the prover in the proof. + eval_pos = ((r * eval_pos * 2) - eval_neg * (r * (Fr(1) - u) - u)) / (r * (Fr(1) - u) + u); + } + + return eval_pos; // return A₀(r) + } + + /** + * @brief Computes two commitments to A₀ partially evaluated in r and -r. + * + * @param batched_f batched commitment to non-shifted polynomials + * @param batched_g batched commitment to to-be-shifted polynomials + * @param r evaluation point at which we have partially evaluated A₀ at r and -r. + * @return std::pair c0_r_pos, c0_r_neg + */ static std::pair compute_simulated_commitments(GroupElement& batched_f, GroupElement& batched_g, - Fr r); + Fr r) + { + // C₀ᵣ₊ = [F] + r⁻¹⋅[G] + GroupElement C0_r_pos = batched_f; + // C₀ᵣ₋ = [F] - r⁻¹⋅[G] + GroupElement C0_r_neg = batched_f; + Fr r_inv = r.invert(); + + // TODO(luke): reinstate some kind of !batched_g.is_point_at_infinity() check for stdlib types? This is mostly + // relevant for Gemini unit tests since in practice batched_g != zero (i.e. we will always have shifted polys). + bool batched_g_is_point_at_infinity = false; + if constexpr (!Curve::is_stdlib_type) { // Note: required for Gemini tests with no shifts + batched_g_is_point_at_infinity = batched_g.is_point_at_infinity(); + } + if (!batched_g_is_point_at_infinity) { + batched_g = batched_g * r_inv; + C0_r_pos += batched_g; + C0_r_neg -= batched_g; + } + + return { C0_r_pos, C0_r_neg }; + } + }; // namespace proof_system::honk::pcs::gemini extern template class GeminiProver_; extern template class GeminiProver_; -extern template class GeminiVerifier_; -extern template class GeminiVerifier_; - } // namespace proof_system::honk::pcs::gemini diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/pcs/kzg/kzg.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/pcs/kzg/kzg.hpp index dea5508c352..ae0ee53a517 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/pcs/kzg/kzg.hpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/pcs/kzg/kzg.hpp @@ -1,10 +1,10 @@ #pragma once #include "../claim.hpp" -#include "barretenberg/honk/transcript/transcript.hpp" -#include "barretenberg/polynomials/polynomial.hpp" #include "barretenberg/honk/pcs/commitment_key.hpp" #include "barretenberg/honk/pcs/verification_key.hpp" +#include "barretenberg/honk/transcript/transcript.hpp" +#include "barretenberg/polynomials/polynomial.hpp" #include #include @@ -46,7 +46,6 @@ template class KZG { /** * @brief Computes the KZG verification for an opening claim of a single polynomial commitment - * This reduction is non-interactive and always succeeds. * * @param vk is the verification key which has a pairing check function * @param claim OpeningClaim ({r, v}, C) @@ -65,5 +64,39 @@ template class KZG { return vk->pairing_check(lhs, rhs); }; + + /** + * @brief Computes the input points for the pairing check needed to verify a KZG opening claim of a single + * polynomial commitment. This reduction is non-interactive and always succeeds. + * @details This is used in the recursive setting where we want to "aggregate" proofs, not verify them. + * + * @param claim OpeningClaim ({r, v}, C) + * @return {P₀, P₁} where + * - P₀ = C − v⋅[1]₁ + r⋅[x]₁ + * - P₁ = [Q(x)]₁ + */ + static std::array compute_pairing_points(const OpeningClaim& claim, + auto& verifier_transcript) + { + auto quotient_commitment = verifier_transcript.template receive_from_prover("KZG:W"); + + auto lhs = claim.commitment + (quotient_commitment * claim.opening_pair.challenge); + // Add the evaluation point contribution v⋅[1]₁. + // Note: In the recursive setting, we only add the contribution if it is not the point at infinity (i.e. if the + // evaluation is not equal to zero). + // TODO(luke): What is the proper way to handle this? Contraints to show scalar (evaluation) is zero? + if constexpr (Curve::is_stdlib_type) { + if (!claim.opening_pair.evaluation.get_value().is_zero()) { + auto ctx = verifier_transcript.builder; + lhs -= GroupElement::one(ctx) * claim.opening_pair.evaluation; + } + } else { + lhs -= GroupElement::one() * claim.opening_pair.evaluation; + } + + auto rhs = -quotient_commitment; + + return { lhs, rhs }; + }; }; } // namespace proof_system::honk::pcs::kzg diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/pcs/shplonk/shplonk.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/pcs/shplonk/shplonk.hpp index 12bcc5ad1df..f4b11815c43 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/pcs/shplonk/shplonk.hpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/pcs/shplonk/shplonk.hpp @@ -1,8 +1,8 @@ #pragma once #include "barretenberg/honk/pcs/claim.hpp" -#include "barretenberg/honk/transcript/transcript.hpp" #include "barretenberg/honk/pcs/commitment_key.hpp" #include "barretenberg/honk/pcs/verification_key.hpp" +#include "barretenberg/honk/transcript/transcript.hpp" /** * @brief Reduces multiple claims about commitments, each opened at a single point @@ -162,9 +162,10 @@ template class ShplonkVerifier_ { * @return OpeningClaim */ static OpeningClaim reduce_verification(std::shared_ptr vk, - std::span> claims, - VerifierTranscript& transcript) + std::span> claims, + auto& transcript) { + const size_t num_claims = claims.size(); const Fr nu = transcript.get_challenge("Shplonk:nu"); @@ -179,21 +180,35 @@ template class ShplonkVerifier_ { // = [Q] - ∑ⱼ (1/zⱼ(r))[Bⱼ] + G₀ [1] // G₀ = ∑ⱼ ρʲ ⋅ vⱼ / ( r − xⱼ ) - Fr G_commitment_constant{ Fr::zero() }; + auto G_commitment_constant = Fr(0); // [G] = [Q] - ∑ⱼ ρʲ / ( r − xⱼ )⋅[fⱼ] + G₀⋅[1] // = [Q] - [∑ⱼ ρʲ ⋅ ( fⱼ(X) − vⱼ) / ( r − xⱼ )] GroupElement G_commitment = Q_commitment; - // {ẑⱼ(r)}ⱼ , where ẑⱼ(r) = 1/zⱼ(r) = 1/(r - xⱼ) - std::vector inverse_vanishing_evals; - inverse_vanishing_evals.reserve(num_claims); + // Compute {ẑⱼ(r)}ⱼ , where ẑⱼ(r) = 1/zⱼ(r) = 1/(r - xⱼ) + std::vector vanishing_evals; + vanishing_evals.reserve(num_claims); for (const auto& claim : claims) { - inverse_vanishing_evals.emplace_back(z_challenge - claim.opening_pair.challenge); + vanishing_evals.emplace_back(z_challenge - claim.opening_pair.challenge); + } + // If recursion, invert elements individually, otherwise batch invert. (Inversion is cheap in circuits since we + // need only prove the correctness of a known inverse, we do not emulate its computation. Hence no need for + // batch inversion). + std::vector inverse_vanishing_evals; + if constexpr (Curve::is_stdlib_type) { + for (const auto& val : vanishing_evals) { + inverse_vanishing_evals.emplace_back(val.invert()); + } + } else { + Fr::batch_invert(vanishing_evals); + inverse_vanishing_evals = vanishing_evals; } - Fr::batch_invert(inverse_vanishing_evals); - Fr current_nu{ Fr::one() }; + auto current_nu = Fr(1); + // Note: commitments and scalars vectors used only in recursion setting for batch mul + std::vector commitments; + std::vector scalars; for (size_t j = 0; j < num_claims; ++j) { // (Cⱼ, xⱼ, vⱼ) const auto& [opening_pair, commitment] = claims[j]; @@ -202,18 +217,41 @@ template class ShplonkVerifier_ { // G₀ += ρʲ / ( r − xⱼ ) ⋅ vⱼ G_commitment_constant += scaling_factor * opening_pair.evaluation; - // [G] -= ρʲ / ( r − xⱼ )⋅[fⱼ] - G_commitment -= commitment * scaling_factor; + + // If recursion, store MSM inputs for batch mul, otherwise accumulate directly + if constexpr (Curve::is_stdlib_type) { + commitments.emplace_back(commitment); + scalars.emplace_back(scaling_factor); + } else { + // [G] -= ρʲ / ( r − xⱼ )⋅[fⱼ] + G_commitment -= commitment * scaling_factor; + } current_nu *= nu; } + + // If recursion, do batch mul to compute [G] -= ∑ⱼ ρʲ / ( r − xⱼ )⋅[fⱼ] + if constexpr (Curve::is_stdlib_type) { + G_commitment -= GroupElement::batch_mul(commitments, scalars); + } + // [G] += G₀⋅[1] = [G] + (∑ⱼ ρʲ ⋅ vⱼ / ( r − xⱼ ))⋅[1] + Fr evaluation_zero; // 0 \in Fr + GroupElement group_one; // [1] + if constexpr (Curve::is_stdlib_type) { + auto ctx = transcript.builder; + evaluation_zero = Fr::from_witness(ctx, 0); + group_one = GroupElement::one(ctx); + } else { + // GroupElement sort_of_one{ x, y }; + evaluation_zero = Fr(0); + group_one = vk->srs->get_first_g1(); + } - // GroupElement sort_of_one{ x, y }; - G_commitment += vk->srs->get_first_g1() * G_commitment_constant; + G_commitment += group_one * G_commitment_constant; // Return opening pair (z, 0) and commitment [G] - return { { z_challenge, Fr::zero() }, G_commitment }; + return { { z_challenge, evaluation_zero }, G_commitment }; }; }; } // namespace proof_system::honk::pcs::shplonk diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/proof_system/ultra_verifier.cpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/proof_system/ultra_verifier.cpp index ab6a6219df2..565df400d8f 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/proof_system/ultra_verifier.cpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/proof_system/ultra_verifier.cpp @@ -111,9 +111,9 @@ template bool UltraVerifier_::verify_proof(const plonk commitments.z_lookup = transcript.template receive_from_prover(commitment_labels.z_lookup); // Execute Sumcheck Verifier - auto sumcheck = SumcheckVerifier(circuit_size, transcript); + auto sumcheck = SumcheckVerifier(circuit_size); - std::optional sumcheck_output = sumcheck.verify(relation_parameters); + std::optional sumcheck_output = sumcheck.verify(relation_parameters, transcript); // If Sumcheck does not return an output, sumcheck verification has failed if (!sumcheck_output.has_value()) { @@ -159,10 +159,10 @@ template bool UltraVerifier_::verify_proof(const plonk // - d+1 commitments [Fold_{r}^(0)], [Fold_{-r}^(0)], and [Fold^(l)], l = 1:d-1 // - d+1 evaluations a_0_pos, and a_l, l = 0:d-1 auto gemini_claim = Gemini::reduce_verification(multivariate_challenge, - batched_evaluation, - batched_commitment_unshifted, - batched_commitment_to_be_shifted, - transcript); + batched_evaluation, + batched_commitment_unshifted, + batched_commitment_to_be_shifted, + transcript); // Produce a Shplonk claim: commitment [Q] - [Q_z], evaluation zero (at random challenge z) auto shplonk_claim = Shplonk::reduce_verification(pcs_verification_key, gemini_claim, transcript); diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/proof_system/verifier.cpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/proof_system/verifier.cpp index 6631eb30dde..a15b968a414 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/proof_system/verifier.cpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/proof_system/verifier.cpp @@ -107,8 +107,8 @@ template bool StandardVerifier_::verify_proof(const pl commitments.z_perm = transcript.template receive_from_prover(commitment_labels.z_perm); // Execute Sumcheck Verifier - auto sumcheck = SumcheckVerifier(circuit_size, transcript); - std::optional sumcheck_output = sumcheck.verify(relation_parameters); + auto sumcheck = SumcheckVerifier(circuit_size); + std::optional sumcheck_output = sumcheck.verify(relation_parameters, transcript); // If Sumcheck does not return an output, sumcheck verification has failed if (!sumcheck_output.has_value()) { diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/sumcheck/polynomials/barycentric_data.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/sumcheck/polynomials/barycentric_data.hpp index 6ab2a0f773d..319ba0f287a 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/sumcheck/polynomials/barycentric_data.hpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/sumcheck/polynomials/barycentric_data.hpp @@ -3,6 +3,12 @@ #include #include +// TODO(#674): We need the functionality of BarycentricData for both field (native) and field_t (stdlib). The former is +// is compatible with constexpr operations, and the former is not. The functions for computing the +// pre-computable arrays in BarycentricData need to be constexpr and it takes some trickery to share these functions +// with the non-constexpr setting. Right now everything is more or less duplicated across BarycentricDataCompileTime and +// BarycentricDataRunTime. There should be a way to share more of the logic. + /* IMPROVEMENT(Cody): This could or should be improved in various ways. In no particular order: 1) Edge cases are not considered. One non-use case situation (I forget which) leads to a segfault. @@ -18,7 +24,7 @@ namespace proof_system::honk::sumcheck { * simplify the implementation a bit. * NOTE: if we use this approach in the recursive setting, will use Plookup? */ -template class BarycentricData { +template class BarycentricDataCompileTime { public: static constexpr size_t big_domain_size = std::max(domain_size, num_evals); @@ -195,4 +201,209 @@ template class BarycentricData return result; }; }; + +template class BarycentricDataRunTime { + public: + static constexpr size_t big_domain_size = std::max(domain_size, num_evals); + + /** + * Static constexpr methods for computing arrays of precomputable data used for barycentric extension and evaluation + */ + + // build big_domain, currently the set of x_i in {0, 1, ..., t-1} + static std::array construct_big_domain() + { + std::array result; + for (size_t i = 0; i < big_domain_size; ++i) { + result[i] = static_cast(i); + } + return result; + } + + // build set of lagrange_denominators d_i = \prod_{j!=i} x_i - x_j + static std::array construct_lagrange_denominators(const auto& big_domain) + { + std::array result; + for (size_t i = 0; i != domain_size; ++i) { + result[i] = 1; + for (size_t j = 0; j != domain_size; ++j) { + if (j != i) { + result[i] *= big_domain[i] - big_domain[j]; + } + } + } + return result; + } + + static std::array batch_invert(const std::array& coeffs) + { + constexpr size_t n = domain_size * num_evals; + std::array temporaries{}; + std::array skipped{}; + Fr accumulator = 1; + for (size_t i = 0; i < n; ++i) { + temporaries[i] = accumulator; + if (coeffs[i].get_value() == 0) { + skipped[i] = true; + } else { + skipped[i] = false; + accumulator *= coeffs[i]; + } + } + accumulator = Fr(1) / accumulator; + std::array result{}; + Fr T0; + for (size_t i = n - 1; i < n; --i) { + if (!skipped[i]) { + T0 = accumulator * temporaries[i]; + accumulator *= coeffs[i]; + result[i] = T0; + } + } + return result; + } + // for each x_k in the big domain, build set of domain size-many denominator inverses + // 1/(d_i*(x_k - x_j)). will multiply against each of these (rather than to divide by something) + // for each barycentric evaluation + static std::array construct_denominator_inverses(const auto& big_domain, + const auto& lagrange_denominators) + { + std::array result{}; // default init to 0 since below does not init all elements + for (size_t k = domain_size; k < num_evals; ++k) { + for (size_t j = 0; j < domain_size; ++j) { + Fr inv = lagrange_denominators[j]; + inv *= (big_domain[k] - big_domain[j]); + result[k * domain_size + j] = inv; + } + } + return batch_invert(result); + } + + // get full numerator values + // full numerator is M(x) = \prod_{i} (x-x_i) + // these will be zero for i < domain_size, but that's ok because + // at such entries we will already have the evaluations of the polynomial + static std::array construct_full_numerator_values(const auto& big_domain) + { + std::array result; + for (size_t i = 0; i != num_evals; ++i) { + result[i] = 1; + Fr v_i = i; + for (size_t j = 0; j != domain_size; ++j) { + result[i] *= v_i - big_domain[j]; + } + } + return result; + } + + inline static const auto big_domain = construct_big_domain(); + inline static const auto lagrange_denominators = construct_lagrange_denominators(big_domain); + inline static const auto precomputed_denominator_inverses = + construct_denominator_inverses(big_domain, lagrange_denominators); + inline static const auto full_numerator_values = construct_full_numerator_values(big_domain); + + /** + * @brief Given A univariate f represented by {f(0), ..., f(t-1)}, compute {f(t), ..., f(u-1)} + * and return the Univariate represented by {f(0), ..., f(u-1)}. + * + * @details Write v_i = f(x_i) on a the domain {x_0, ..., x_{t-1}}. To efficiently compute the needed values of f, + * we use the barycentric formula + * - f(x) = B(x) Σ_{i=0}^{t-1} v_i / (d_i*(x-x_i)) + * where + * - B(x) = Π_{i=0}^{t-1} (x-x_i) + * - d_i = Π_{j ∈ {0, ..., t-1}, j≠i} (x_i-x_j) for i ∈ {0, ..., t-1} + * + * NOTE: just taking x_i = i for now and possibly forever. Hence can significantly optimize: + * extending an Edge f = v0(1-X) + v1X to a new value involves just one addition and a subtraction: + * setting Δ = v1-v0, the values of f(X) are + * f(0)=v0, f(1)= v0 + Δ, v2 = f(1) + Δ, v3 = f(2) + Δ... + * + */ + Univariate extend(Univariate f) + { + // ASSERT(u>t); + Univariate result; + + for (size_t k = 0; k != domain_size; ++k) { + result.value_at(k) = f.value_at(k); + } + + for (size_t k = domain_size; k != num_evals; ++k) { + result.value_at(k) = 0; + // compute each term v_j / (d_j*(x-x_j)) of the sum + for (size_t j = 0; j != domain_size; ++j) { + Fr term = f.value_at(j); + term *= precomputed_denominator_inverses[domain_size * k + j]; + result.value_at(k) += term; + } + // scale the sum by the the value of of B(x) + result.value_at(k) *= full_numerator_values[k]; + } + return result; + } + + /** + * @brief Evaluate a univariate at a point u not known at compile time + * and assumed not to be in the domain (else we divide by zero). + * @param f + * @return Fr + */ + Fr evaluate(Univariate& f, const Fr& u) + { + + Fr full_numerator_value = 1; + for (size_t i = 0; i != domain_size; ++i) { + full_numerator_value *= u - i; + } + + // build set of domain size-many denominator inverses 1/(d_i*(x_k - x_j)). will multiply against each of + // these (rather than to divide by something) for each barycentric evaluation + std::array denominator_inverses; + for (size_t i = 0; i != domain_size; ++i) { + Fr inv = lagrange_denominators[i]; + inv *= u - big_domain[i]; // warning: need to avoid zero here + inv = Fr(1) / inv; + denominator_inverses[i] = inv; + } + + Fr result = 0; + // compute each term v_j / (d_j*(x-x_j)) of the sum + for (size_t i = 0; i != domain_size; ++i) { + Fr term = f.value_at(i); + term *= denominator_inverses[i]; + result += term; + } + // scale the sum by the the value of of B(x) + result *= full_numerator_value; + return result; + }; +}; + +/** + * @brief Helper to determine whether input is bberg::field type + * + * @tparam T + */ +template struct is_field_type { + static constexpr bool value = false; +}; + +template struct is_field_type> { + static constexpr bool value = true; +}; + +template inline constexpr bool is_field_type_v = is_field_type::value; + +/** + * @brief Exposes BarycentricData with compile time arrays if the type is bberg::field and runtime arrays otherwise + * @details This method is also needed for stdlib field, for which the arrays are not compile time computable + * @tparam Fr + * @tparam domain_size + * @tparam num_evals + */ +template +using BarycentricData = std::conditional_t, + BarycentricDataCompileTime, + BarycentricDataRunTime>; + } // namespace proof_system::honk::sumcheck diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/sumcheck/polynomials/barycentric_data.test.cpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/sumcheck/polynomials/barycentric_data.test.cpp index c9925f7b79d..91d67db9c4f 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/sumcheck/polynomials/barycentric_data.test.cpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/sumcheck/polynomials/barycentric_data.test.cpp @@ -1,5 +1,5 @@ -#include "barretenberg/ecc/curves/bn254/fr.hpp" #include "barycentric_data.hpp" +#include "barretenberg/ecc/curves/bn254/fr.hpp" #include "barretenberg/numeric/random/engine.hpp" #include @@ -14,6 +14,20 @@ TYPED_TEST_SUITE(BarycentricDataTests, FieldTypes); #define BARYCENTIC_DATA_TESTS_TYPE_ALIASES using FF = TypeParam; +/** + * @brief Ensure auxilliary arrays (e.g. big_domain) are computed at compile time if possible (i.e. if FF is a native + * field) + * + */ +TYPED_TEST(BarycentricDataTests, CompileTimeComputation) +{ + BARYCENTIC_DATA_TESTS_TYPE_ALIASES + const size_t domain_size(2); + const size_t num_evals(10); + + static_assert(BarycentricData::big_domain[5] == 5); +} + TYPED_TEST(BarycentricDataTests, Extend) { BARYCENTIC_DATA_TESTS_TYPE_ALIASES diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/sumcheck/polynomials/pow.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/sumcheck/polynomials/pow.hpp index 654c348753f..dd84d7cebc8 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/sumcheck/polynomials/pow.hpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/sumcheck/polynomials/pow.hpp @@ -101,7 +101,7 @@ template struct PowUnivariate { // c_{l}, initialized as c_{0} = 1 // c_{l} = ∏_{0 ≤ k < l-1} ( (1-u_{k}) + u_{k}⋅ζ_{k} ) // At round d-1, equals pow(u_{0}, ..., u_{d-1}). - FF partial_evaluation_constant = FF::one(); + FF partial_evaluation_constant = FF(1); // Initialize with the random zeta explicit PowUnivariate(FF zeta_pow) @@ -110,7 +110,7 @@ template struct PowUnivariate { {} // Evaluate the monomial ((1−X_{l}) + X_{l}⋅ζ_{l}) in the challenge point X_{l}=u_{l}. - FF univariate_eval(FF challenge) const { return (FF::one() + (challenge * (zeta_pow - FF::one()))); }; + FF univariate_eval(FF challenge) const { return (FF(1) + (challenge * (zeta_pow - FF(1)))); }; /** * @brief Parially evaluate the polynomial in the new challenge, by updating the constant c_{l} -> c_{l+1}. @@ -122,7 +122,9 @@ template struct PowUnivariate { { FF current_univariate_eval = univariate_eval(challenge); zeta_pow = zeta_pow_sqr; - zeta_pow_sqr.self_sqr(); + // TODO(luke): for native FF, this could be self_sqr() + zeta_pow_sqr = zeta_pow_sqr.sqr(); + partial_evaluation_constant *= current_univariate_eval; } }; diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/sumcheck/relations/lookup_relation.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/sumcheck/relations/lookup_relation.hpp index e54bff7daec..56ff50447fd 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/sumcheck/relations/lookup_relation.hpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/sumcheck/relations/lookup_relation.hpp @@ -70,7 +70,7 @@ template class LookupRelationBase { const auto eta_sqr = eta * eta; const auto eta_cube = eta_sqr * eta; - const auto one_plus_beta = FF::one() + beta; + const auto one_plus_beta = FF(1) + beta; const auto gamma_by_one_plus_beta = gamma * one_plus_beta; auto w_1 = get_view(extended_edges.w_l, index); @@ -131,7 +131,7 @@ template class LookupRelationBase { const auto& beta = relation_parameters.beta; const auto& gamma = relation_parameters.gamma; - const auto one_plus_beta = FF::one() + beta; + const auto one_plus_beta = FF(1) + beta; const auto gamma_by_one_plus_beta = gamma * one_plus_beta; // Contribution (1) diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/sumcheck/relations/relation_parameters.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/sumcheck/relations/relation_parameters.hpp index 863688560cf..a575a999a76 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/sumcheck/relations/relation_parameters.hpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/sumcheck/relations/relation_parameters.hpp @@ -9,10 +9,10 @@ namespace proof_system::honk::sumcheck { * @tparam FF */ template struct RelationParameters { - FF eta = FF::zero(); // Lookup - FF beta = FF::zero(); // Permutation + Lookup - FF gamma = FF::zero(); // Permutation + Lookup - FF public_input_delta = FF::zero(); // Permutation - FF lookup_grand_product_delta = FF::zero(); // Lookup + FF eta = FF(0); // Lookup + FF beta = FF(0); // Permutation + Lookup + FF gamma = FF(0); // Permutation + Lookup + FF public_input_delta = FF(0); // Permutation + FF lookup_grand_product_delta = FF(0); // Lookup }; } // namespace proof_system::honk::sumcheck diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/sumcheck/sumcheck.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/sumcheck/sumcheck.hpp index c1d27fd5462..7d21b09d001 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/sumcheck/sumcheck.hpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/sumcheck/sumcheck.hpp @@ -162,14 +162,12 @@ template class SumcheckVerifier { static constexpr size_t MAX_RANDOM_RELATION_LENGTH = Flavor::MAX_RANDOM_RELATION_LENGTH; static constexpr size_t NUM_POLYNOMIALS = Flavor::NUM_ALL_ENTITIES; - VerifierTranscript& transcript; const size_t multivariate_d; SumcheckVerifierRound round; - // verifier instantiates sumcheck with circuit size and a verifier transcript - explicit SumcheckVerifier(size_t multivariate_n, VerifierTranscript& transcript) - : transcript(transcript) - , multivariate_d(numeric::get_msb(multivariate_n)) + // verifier instantiates sumcheck with circuit size + explicit SumcheckVerifier(size_t multivariate_n) + : multivariate_d(numeric::get_msb(multivariate_n)) , round(){}; /** @@ -178,8 +176,10 @@ template class SumcheckVerifier { * target sum. * * @details If verification fails, returns std::nullopt, otherwise returns SumcheckOutput + * @param relation_parameters + * @param transcript */ - std::optional> verify(const RelationParameters& relation_parameters) + std::optional> verify(const RelationParameters& relation_parameters, auto& transcript) { bool verified(true); @@ -221,7 +221,14 @@ template class SumcheckVerifier { FF full_honk_relation_purported_value = round.compute_full_honk_relation_purported_value( purported_evaluations._data, relation_parameters, pow_univariate, alpha); - verified = verified && (full_honk_relation_purported_value == round.target_total_sum); + + bool checked = false; + if constexpr (IsRecursiveFlavor) { + checked = (full_honk_relation_purported_value == round.target_total_sum).get_value(); + } else { + checked = (full_honk_relation_purported_value == round.target_total_sum); + } + verified = verified && checked; if (!verified) { return std::nullopt; } diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/sumcheck/sumcheck.test.cpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/sumcheck/sumcheck.test.cpp index ad21f458b97..59035ec889d 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/sumcheck/sumcheck.test.cpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/sumcheck/sumcheck.test.cpp @@ -329,9 +329,9 @@ TEST_F(SumcheckTests, ProverAndVerifier) auto verifier_transcript = VerifierTranscript::init_empty(prover_transcript); - auto sumcheck_verifier = SumcheckVerifier(multivariate_n, verifier_transcript); + auto sumcheck_verifier = SumcheckVerifier(multivariate_n); - std::optional verifier_output = sumcheck_verifier.verify(relation_parameters); + std::optional verifier_output = sumcheck_verifier.verify(relation_parameters, verifier_transcript); ASSERT_TRUE(verifier_output.has_value()); ASSERT_EQ(prover_output, *verifier_output); @@ -403,9 +403,9 @@ TEST_F(SumcheckTests, ProverAndVerifierLonger) auto verifier_transcript = VerifierTranscript::init_empty(prover_transcript); - auto sumcheck_verifier = SumcheckVerifier(multivariate_n, verifier_transcript); + auto sumcheck_verifier = SumcheckVerifier(multivariate_n); - std::optional verifier_output = sumcheck_verifier.verify(relation_parameters); + std::optional verifier_output = sumcheck_verifier.verify(relation_parameters, verifier_transcript); EXPECT_EQ(verifier_output.has_value(), expect_verified); }; @@ -489,9 +489,9 @@ TEST_F(SumcheckTests, RealCircuitStandard) auto verifier_transcript = VerifierTranscript::init_empty(prover_transcript); - auto sumcheck_verifier = SumcheckVerifier(prover.key->circuit_size, verifier_transcript); + auto sumcheck_verifier = SumcheckVerifier(prover.key->circuit_size); - std::optional verifier_output = sumcheck_verifier.verify(relation_parameters); + std::optional verifier_output = sumcheck_verifier.verify(relation_parameters, verifier_transcript); ASSERT_TRUE(verifier_output.has_value()); } @@ -703,9 +703,9 @@ TEST_F(SumcheckTests, RealCircuitUltra) auto verifier_transcript = VerifierTranscript::init_empty(prover_transcript); - auto sumcheck_verifier = SumcheckVerifier(prover.key->circuit_size, verifier_transcript); + auto sumcheck_verifier = SumcheckVerifier(prover.key->circuit_size); - std::optional verifier_output = sumcheck_verifier.verify(relation_parameters); + std::optional verifier_output = sumcheck_verifier.verify(relation_parameters, verifier_transcript); ASSERT_TRUE(verifier_output.has_value()); } diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/sumcheck/sumcheck_round.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/sumcheck/sumcheck_round.hpp index e3444d61835..4dc27e075f8 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/sumcheck/sumcheck_round.hpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/sumcheck/sumcheck_round.hpp @@ -340,8 +340,11 @@ template class SumcheckProverRound { template static constexpr void add_tuples(std::tuple& tuple_1, const std::tuple& tuple_2) { - auto add_tuples_helper = [&](std::index_sequence) { ((std::get(tuple_1) += std::get(tuple_2)), ...); }; - + auto add_tuples_helper = [&](std::index_sequence) + { + ((std::get(tuple_1) += std::get(tuple_2)), ...); + }; + add_tuples_helper(std::make_index_sequence{}); } @@ -421,7 +424,15 @@ template class SumcheckVerifierRound { // S^{l}(1) = ( (1−1) + 1⋅ζ^{ 2^l } ) ⋅ T^{l}(1) = ζ^{ 2^l } ⋅ T^{l}(1) FF total_sum = univariate.value_at(0) + univariate.value_at(1); // target_total_sum = sigma_{l} = - bool sumcheck_round_failed = (target_total_sum != total_sum); + // TODO(#673): Conditionals like this can go away once native verification is is just recursive verification + // with a simulated builder. + bool sumcheck_round_failed(false); + if constexpr (IsRecursiveFlavor) { + sumcheck_round_failed = (target_total_sum != total_sum).get_value(); + } else { + sumcheck_round_failed = (target_total_sum != total_sum); + } + round_failed = round_failed || sumcheck_round_failed; return !sumcheck_round_failed; }; @@ -441,6 +452,7 @@ template class SumcheckVerifierRound { auto barycentric = BarycentricData(); // Evaluate T^{l}(u_{l}) target_total_sum = barycentric.evaluate(univariate, round_challenge); + return target_total_sum; } diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/utils/grand_product_delta.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/utils/grand_product_delta.hpp index 18377c4a7db..9ce1b29a3a4 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/utils/grand_product_delta.hpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/utils/grand_product_delta.hpp @@ -19,12 +19,12 @@ template typename Flavor::FF compute_public_input_delta(std::span public_inputs, const typename Flavor::FF& beta, const typename Flavor::FF& gamma, - const size_t domain_size, + const auto domain_size, size_t offset = 0) { using Field = typename Flavor::FF; - Field numerator = Field::one(); - Field denominator = Field::one(); + Field numerator = Field(1); + Field denominator = Field(1); // Let m be the number of public inputs x₀,…, xₘ₋₁. // Recall that we broke the permutation σ⁰ by changing the mapping @@ -77,7 +77,7 @@ typename Flavor::FF compute_public_input_delta(std::span -Field compute_lookup_grand_product_delta(const Field& beta, const Field& gamma, const size_t domain_size) +Field compute_lookup_grand_product_delta(const Field& beta, const Field& gamma, const auto domain_size) { Field gamma_by_one_plus_beta = gamma * (Field(1) + beta); // γ(1 + β) return gamma_by_one_plus_beta.pow(domain_size); // (γ(1 + β))^n diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/flavor/flavor.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/flavor/flavor.hpp index e7bc88628e9..45d019a417b 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/flavor/flavor.hpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/flavor/flavor.hpp @@ -250,25 +250,6 @@ template static constexpr auto } } -/** - * @brief Recursive helper function to instantiate BarycentricData to extend each Relation in a tuple - * @details Instantiate with lengths 2, 3, ... ExtendedLength - * @note The purpose of this function is simply to instantiate some BarycentricData so that the static member - * arrays are computed at compile time. It thus does not need a return value. It's awkward however to make - * void functions execute at compile time so we make it return true upon completion and wrap the call in a - * static_assert to ensure it has executed correctly. - */ -template static constexpr bool instantiate_barycentric_utils() -{ - if constexpr (Length > ExtendedLength) { // include ExtendedLength - return true; // Return true when finished - } else { - // We dont need to keep the result, we just want to ensure compile time computation of static members - [[maybe_unused]] auto barycentric_data = sumcheck::BarycentricData{}; - return instantiate_barycentric_utils(); - } -} - } // namespace proof_system::honk::flavor // Forward declare honk flavors @@ -278,6 +259,7 @@ class StandardGrumpkin; class Ultra; class UltraGrumpkin; class GoblinUltra; +class UltraRecursive; } // namespace proof_system::honk::flavor // Forward declare plonk flavors @@ -309,6 +291,9 @@ concept IsUltraFlavor = IsAnyOf concept IsGoblinFlavor = IsAnyOf; +template +concept IsRecursiveFlavor = IsAnyOf; + template concept IsGrumpkinFlavor = IsAnyOf; template concept StandardFlavor = IsAnyOf; diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/primitives/curves/bn254.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/primitives/curves/bn254.hpp index 2f2f7dcdce1..27ed0920797 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/primitives/curves/bn254.hpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/primitives/curves/bn254.hpp @@ -9,6 +9,10 @@ namespace stdlib { template struct bn254 { static constexpr proof_system::CurveType type = proof_system::CurveType::BN254; + // TODO(#673): This flag is temporary. It is needed in the verifier classes (GeminiVerifier, etc.) while these + // classes are instantiated with "native" curve types. Eventually, the verifier classes will be instantiated only + // with stdlib types, and "native" verification will be acheived via a simulated builder. + static constexpr bool is_stdlib_type = true; // Corresponding native types (used exclusively for testing) using ScalarFieldNative = curve::BN254::ScalarField; diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/transcript/trancript.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/transcript/transcript.hpp similarity index 85% rename from circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/transcript/trancript.hpp rename to circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/transcript/transcript.hpp index be4707af4a5..c26809d4353 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/transcript/trancript.hpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/transcript/transcript.hpp @@ -15,7 +15,7 @@ namespace proof_system::plonk::stdlib::recursion::honk { template class Transcript { public: - using field_pt = field_t; + using field_ct = field_t; using FF = barretenberg::fr; using VerifierTranscript = proof_system::honk::VerifierTranscript; using StdlibTypes = utility::StdlibTypesUtility; @@ -25,6 +25,8 @@ template class Transcript { VerifierTranscript native_transcript; Builder* builder; + Transcript() = default; + Transcript(Builder* builder, auto proof_data) : native_transcript(proof_data) , builder(builder){}; @@ -42,7 +44,7 @@ template class Transcript { * @param labels Names of the challenges to be computed * @return std::array Array of challenges */ - template std::array get_challenges(const Strings&... labels) + template std::array get_challenges(const Strings&... labels) { // Compute the indicated challenges from the native transcript constexpr size_t num_challenges = sizeof...(Strings); @@ -55,7 +57,7 @@ template class Transcript { * since it's a pain and we'll be revamping our hashing anyway. For now, simply convert the native hashes to * stdlib types without adding any hashing constraints. */ - std::array challenges; + std::array challenges; for (size_t i = 0; i < num_challenges; ++i) { challenges[i] = native_challenges[i]; } @@ -67,16 +69,16 @@ template class Transcript { * @brief Compute the single challenge indicated by the input label * * @param label Name of challenge - * @return field_pt Challenge + * @return field_ct Challenge */ - field_pt get_challenge(const std::string& label) + field_ct get_challenge(const std::string& label) { // Compute the indicated challenge from the native transcript auto native_challenge = native_transcript.get_challenge(label); // TODO(1351): Stdlib hashing here... - return field_pt(native_challenge); + return field_ct::from_witness(builder, native_challenge); } /** @@ -88,8 +90,11 @@ template class Transcript { */ template auto receive_from_prover(const std::string& label) { + // Get native type corresponding to input type + using NativeType = typename StdlibTypes::template NativeType::type; + // Extract the native element from the native transcript - T element = native_transcript.template receive_from_prover(label); + NativeType element = native_transcript.template receive_from_prover(label); // Return the corresponding stdlib type return StdlibTypes::from_witness(builder, element); diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/transcript/transcript.test.cpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/transcript/transcript.test.cpp index 33f70306c3a..f32032e499b 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/transcript/transcript.test.cpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/transcript/transcript.test.cpp @@ -2,30 +2,34 @@ #include "barretenberg/ecc/curves/bn254/fr.hpp" #include "barretenberg/ecc/curves/bn254/g1.hpp" +#include "barretenberg/honk/flavor/ultra.hpp" +#include "barretenberg/honk/flavor/ultra_recursive.hpp" #include "barretenberg/honk/sumcheck/polynomials/univariate.hpp" #include "barretenberg/honk/transcript/transcript.hpp" -#include "barretenberg/stdlib/recursion/honk/transcript/trancript.hpp" +#include "barretenberg/stdlib/recursion/honk/transcript/transcript.hpp" namespace proof_system::plonk::stdlib::recursion::honk { using Builder = UltraCircuitBuilder; - +using UltraFlavor = ::proof_system::honk::flavor::Ultra; +using UltraRecursiveFlavor = ::proof_system::honk::flavor::UltraRecursive; using FF = barretenberg::fr; -using Commitment = barretenberg::g1::affine_element; -using Point = barretenberg::g1::element; -constexpr size_t LENGTH = 8; // arbitrary -using Univariate = proof_system::honk::sumcheck::Univariate; using ProverTranscript = ::proof_system::honk::ProverTranscript; using VerifierTranscript = ::proof_system::honk::VerifierTranscript; /** - * @brief Create some mock data and then add it to the transcript in various mock rounds + * @brief Create some mock data; add it to the provided prover transcript in various mock rounds * * @param prover_transcript * @return auto proof_data */ -auto generate_mock_proof_data(auto prover_transcript) +template auto generate_mock_proof_data(auto prover_transcript) { + using FF = typename Flavor::FF; + using Commitment = typename Flavor::Commitment; + using Univariate = typename proof_system::honk::sumcheck::Univariate; + + // Create some mock data to be added to the transcript in several mock rounds uint32_t data = 25; auto scalar = FF::random_element(); auto commitment = Commitment::one(); @@ -58,9 +62,15 @@ auto generate_mock_proof_data(auto prover_transcript) * transcript was initialized. * * @param transcript Either a native or stdlib verifier transcript + * @tparam Flavor + * @tparam LENGTH Length of Univariate to be serialized */ -void perform_mock_verifier_transcript_operations(auto transcript) +template void perform_mock_verifier_transcript_operations(auto transcript) { + using FF = typename Flavor::FF; + using Commitment = typename Flavor::Commitment; + using Univariate = typename proof_system::honk::sumcheck::Univariate; + // round 0 transcript.template receive_from_prover("data"); transcript.get_challenge("alpha"); @@ -78,31 +88,34 @@ void perform_mock_verifier_transcript_operations(auto transcript) /** * @brief Test basic transcript functionality and check circuit * @details Implicitly ensures stdlib interface is identical to native - * @todo(luke): Underlying circuit is nearly trivial until transcript implements hashing constraints + * */ -TEST(stdlib_honk_transcript, basic_transcript_operations) +TEST(RecursiveHonkTranscript, InterfacesMatch) { Builder builder; + constexpr size_t LENGTH = 8; // arbitrary length of Univariate to be serialized + // Instantiate a Prover Transcript and use it to generate some mock proof data ProverTranscript prover_transcript; - auto proof_data = generate_mock_proof_data(prover_transcript); + auto proof_data = generate_mock_proof_data(prover_transcript); // Instantiate a (native) Verifier Transcript with the proof data and perform some mock transcript operations VerifierTranscript native_transcript(proof_data); - perform_mock_verifier_transcript_operations(native_transcript); + perform_mock_verifier_transcript_operations(native_transcript); // Confirm that Prover and Verifier transcripts have generated the same manifest via the operations performed EXPECT_EQ(prover_transcript.get_manifest(), native_transcript.get_manifest()); // Instantiate a stdlib Transcript and perform the same operations Transcript transcript{ &builder, proof_data }; - perform_mock_verifier_transcript_operations(transcript); + perform_mock_verifier_transcript_operations(transcript); - // Confirm that the native and stdlib transcripts have generated the same manifest + // Confirm that the native and stdlib verifier transcripts have generated the same manifest EXPECT_EQ(transcript.get_manifest(), native_transcript.get_manifest()); - // TODO(luke): This doesn't check much of anything until hashing is constrained in the stdlib transcript + // TODO(#1351): The Honk stdlib transcript does not currently lay down contraints for fiat-shamir hashing so + // check_circuit has limited value. EXPECT_TRUE(builder.check_circuit()); } @@ -110,8 +123,15 @@ TEST(stdlib_honk_transcript, basic_transcript_operations) * @brief Check that native and stdlib verifier transcript functions produce equivalent outputs * */ -TEST(stdlib_honk_transcript, return_values) +TEST(RecursiveHonkTranscript, ReturnValuesMatch) { + using FF = barretenberg::fr; + using Commitment = barretenberg::g1::affine_element; + + using field_ct = field_t; + using fq_ct = bigfield; + using element_ct = element; + Builder builder; // Define some mock data for a mock proof @@ -139,11 +159,12 @@ TEST(stdlib_honk_transcript, return_values) auto native_evaluations = native_transcript.template receive_from_prover>("evaluations"); auto [native_alpha, native_beta] = native_transcript.get_challenges("alpha", "beta"); - // Perform the corresponding operations with the stdlib verifier transcript + // Perform the same operations with the stdlib verifier transcript Transcript stdlib_transcript{ &builder, proof_data }; - auto stdlib_scalar = stdlib_transcript.template receive_from_prover("scalar"); - auto stdlib_commitment = stdlib_transcript.template receive_from_prover("commitment"); - auto stdlib_evaluations = stdlib_transcript.template receive_from_prover>("evaluations"); + auto stdlib_scalar = stdlib_transcript.template receive_from_prover("scalar"); + auto stdlib_commitment = stdlib_transcript.template receive_from_prover("commitment"); + auto stdlib_evaluations = + stdlib_transcript.template receive_from_prover>("evaluations"); auto [stdlib_alpha, stdlib_beta] = stdlib_transcript.get_challenges("alpha", "beta"); // Confirm that return values are equivalent @@ -155,5 +176,4 @@ TEST(stdlib_honk_transcript, return_values) EXPECT_EQ(native_alpha, stdlib_alpha.get_value()); EXPECT_EQ(native_beta, stdlib_beta.get_value()); } - } // namespace proof_system::plonk::stdlib::recursion::honk \ No newline at end of file diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/verifier/ultra_recursive_verifier.cpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/verifier/ultra_recursive_verifier.cpp new file mode 100644 index 00000000000..df9e8a199f0 --- /dev/null +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/verifier/ultra_recursive_verifier.cpp @@ -0,0 +1,205 @@ +// #include "./ultra_verifier.hpp" +#include "barretenberg/stdlib/recursion/honk/verifier/ultra_recursive_verifier.hpp" +#include "barretenberg/honk/flavor/standard.hpp" +#include "barretenberg/honk/transcript/transcript.hpp" +#include "barretenberg/honk/utils/grand_product_delta.hpp" +#include "barretenberg/honk/utils/power_polynomial.hpp" +#include "barretenberg/numeric/bitop/get_msb.hpp" + +namespace proof_system::plonk::stdlib::recursion::honk { +template +UltraRecursiveVerifier_::UltraRecursiveVerifier_(Builder* builder, + std::shared_ptr verifier_key) + : key(verifier_key) + , builder(builder) +{} + +template +UltraRecursiveVerifier_::UltraRecursiveVerifier_(UltraRecursiveVerifier_&& other) noexcept + : key(std::move(other.key)) + , pcs_verification_key(std::move(other.pcs_verification_key)) +{} + +template +UltraRecursiveVerifier_& UltraRecursiveVerifier_::operator=(UltraRecursiveVerifier_&& other) noexcept +{ + key = other.key; + pcs_verification_key = (std::move(other.pcs_verification_key)); + commitments.clear(); + pcs_fr_elements.clear(); + return *this; +} + +/** + * @brief This function constructs a recursive verifier circuit for an Ultra Honk proof of a given flavor. + * + */ +template +std::array UltraRecursiveVerifier_::verify_proof(const plonk::proof& proof) +{ + using FF = typename Flavor::FF; + using GroupElement = typename Flavor::GroupElement; + using Commitment = typename Flavor::Commitment; + using Sumcheck = ::proof_system::honk::sumcheck::SumcheckVerifier; + using Curve = typename Flavor::Curve; + using Gemini = ::proof_system::honk::pcs::gemini::GeminiVerifier_; + using Shplonk = ::proof_system::honk::pcs::shplonk::ShplonkVerifier_; + using PCS = typename Flavor::PCS; // note: This can only be KZG + using VerifierCommitments = typename Flavor::VerifierCommitments; + using CommitmentLabels = typename Flavor::CommitmentLabels; + using RelationParams = ::proof_system::honk::sumcheck::RelationParameters; + + RelationParams relation_parameters; + + size_t prev_num_gates = builder->get_num_gates(); + + transcript = Transcript{ builder, proof.proof_data }; + + auto commitments = VerifierCommitments(key); + auto commitment_labels = CommitmentLabels(); + + const auto circuit_size = transcript.template receive_from_prover("circuit_size"); + const auto public_input_size = transcript.template receive_from_prover("public_input_size"); + const auto pub_inputs_offset = transcript.template receive_from_prover("pub_inputs_offset"); + + // For debugging purposes only + ASSERT(static_cast(circuit_size.get_value()) == key->circuit_size); + ASSERT(static_cast(public_input_size.get_value()) == key->num_public_inputs); + + std::vector public_inputs; + for (size_t i = 0; i < static_cast(public_input_size.get_value()); ++i) { + auto public_input_i = transcript.template receive_from_prover("public_input_" + std::to_string(i)); + public_inputs.emplace_back(public_input_i); + } + + // Get commitments to first three wire polynomials + commitments.w_l = transcript.template receive_from_prover(commitment_labels.w_l); + commitments.w_r = transcript.template receive_from_prover(commitment_labels.w_r); + commitments.w_o = transcript.template receive_from_prover(commitment_labels.w_o); + + // If Goblin, get commitments to ECC op wire polynomials + if constexpr (IsGoblinFlavor) { + commitments.ecc_op_wire_1 = + transcript.template receive_from_prover(commitment_labels.ecc_op_wire_1); + commitments.ecc_op_wire_2 = + transcript.template receive_from_prover(commitment_labels.ecc_op_wire_2); + commitments.ecc_op_wire_3 = + transcript.template receive_from_prover(commitment_labels.ecc_op_wire_3); + commitments.ecc_op_wire_4 = + transcript.template receive_from_prover(commitment_labels.ecc_op_wire_4); + } + + // Get challenge for sorted list batching and wire four memory records + auto eta = transcript.get_challenge("eta"); + relation_parameters.eta = eta; + + // Get commitments to sorted list accumulator and fourth wire + commitments.sorted_accum = transcript.template receive_from_prover(commitment_labels.sorted_accum); + commitments.w_4 = transcript.template receive_from_prover(commitment_labels.w_4); + + // Get permutation challenges + auto [beta, gamma] = transcript.get_challenges("beta", "gamma"); + + const FF public_input_delta = proof_system::honk::compute_public_input_delta( + public_inputs, beta, gamma, circuit_size, static_cast(pub_inputs_offset.get_value())); + const FF lookup_grand_product_delta = + proof_system::honk::compute_lookup_grand_product_delta(beta, gamma, circuit_size); + + relation_parameters.beta = beta; + relation_parameters.gamma = gamma; + relation_parameters.public_input_delta = public_input_delta; + relation_parameters.lookup_grand_product_delta = lookup_grand_product_delta; + + // Get commitment to permutation and lookup grand products + commitments.z_perm = transcript.template receive_from_prover(commitment_labels.z_perm); + commitments.z_lookup = transcript.template receive_from_prover(commitment_labels.z_lookup); + + // Execute Sumcheck Verifier + auto sumcheck = Sumcheck(static_cast(circuit_size.get_value())); + + std::optional sumcheck_output = sumcheck.verify(relation_parameters, transcript); + + info("Sumcheck: num gates = ", builder->get_num_gates() - prev_num_gates); + prev_num_gates = builder->get_num_gates(); + + // If Sumcheck does not return an output, sumcheck verification has failed + ASSERT(sumcheck_output.has_value()); // TODO(luke): Appropriate way to handle this in circuit? + + // Extract multivariate opening point u = (u_0, ..., u_{d-1}) and purported multivariate evaluations at u + auto [multivariate_challenge, purported_evaluations] = *sumcheck_output; + + // Compute powers of batching challenge rho + FF rho = transcript.get_challenge("rho"); + std::vector rhos = ::proof_system::honk::pcs::gemini::powers_of_rho(rho, Flavor::NUM_ALL_ENTITIES); + + // Compute batched multivariate evaluation + FF batched_evaluation = FF(0); + size_t evaluation_idx = 0; + for (auto& value : purported_evaluations.get_unshifted_then_shifted()) { + batched_evaluation += value * rhos[evaluation_idx]; + ++evaluation_idx; + } + + info("Batched eval: num gates = ", builder->get_num_gates() - prev_num_gates); + prev_num_gates = builder->get_num_gates(); + + // Compute batched commitments needed for input to Gemini. + // Note: For efficiency in emulating the construction of the batched commitments, we want to perform a batch mul + // rather than naively accumulate the points one by one. To do this, we collect the points and scalars required for + // each MSM then perform the two batch muls. + const size_t NUM_UNSHIFTED = commitments.get_unshifted().size(); + const size_t NUM_TO_BE_SHIFTED = commitments.get_to_be_shifted().size(); + std::vector scalars_unshifted; + std::vector scalars_to_be_shifted; + size_t idx = 0; + for (size_t i = 0; i < NUM_UNSHIFTED; ++i) { + scalars_unshifted.emplace_back(rhos[idx++]); + } + for (size_t i = 0; i < NUM_TO_BE_SHIFTED; ++i) { + scalars_to_be_shifted.emplace_back(rhos[idx++]); + } + // TODO(luke): The powers_of_rho fctn does not set the context of rhos[0] = FF(1) so we do it explicitly here. Can + // we do something silly like set it to rho.pow(0) in the fctn to make it work both native and stdlib? + scalars_unshifted[0] = FF::from_witness(builder, 1); + + // Batch the commitments to the unshifted and to-be-shifted polynomials using powers of rho + auto batched_commitment_unshifted = GroupElement::batch_mul(commitments.get_unshifted(), scalars_unshifted); + + info("Batch mul (unshifted): num gates = ", builder->get_num_gates() - prev_num_gates); + prev_num_gates = builder->get_num_gates(); + + auto batched_commitment_to_be_shifted = + GroupElement::batch_mul(commitments.get_to_be_shifted(), scalars_to_be_shifted); + + info("Batch mul (to-be-shited): num gates = ", builder->get_num_gates() - prev_num_gates); + prev_num_gates = builder->get_num_gates(); + + // Produce a Gemini claim consisting of: + // - d+1 commitments [Fold_{r}^(0)], [Fold_{-r}^(0)], and [Fold^(l)], l = 1:d-1 + // - d+1 evaluations a_0_pos, and a_l, l = 0:d-1 + auto gemini_claim = Gemini::reduce_verification(multivariate_challenge, + batched_evaluation, + batched_commitment_unshifted, + batched_commitment_to_be_shifted, + transcript); + + info("Gemini: num gates = ", builder->get_num_gates() - prev_num_gates); + prev_num_gates = builder->get_num_gates(); + + // Produce a Shplonk claim: commitment [Q] - [Q_z], evaluation zero (at random challenge z) + auto shplonk_claim = Shplonk::reduce_verification(pcs_verification_key, gemini_claim, transcript); + + info("Shplonk: num gates = ", builder->get_num_gates() - prev_num_gates); + prev_num_gates = builder->get_num_gates(); + + info("Total: num gates = ", builder->get_num_gates()); + + // Constuct the inputs to the final KZG pairing check + auto pairing_points = PCS::compute_pairing_points(shplonk_claim, transcript); + + return pairing_points; +} + +template class UltraRecursiveVerifier_; + +} // namespace proof_system::plonk::stdlib::recursion::honk diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/verifier/ultra_recursive_verifier.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/verifier/ultra_recursive_verifier.hpp new file mode 100644 index 00000000000..a1eee1d4d44 --- /dev/null +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/verifier/ultra_recursive_verifier.hpp @@ -0,0 +1,42 @@ +#pragma once +#include "barretenberg/honk/flavor/goblin_ultra.hpp" +#include "barretenberg/honk/flavor/ultra.hpp" +#include "barretenberg/honk/flavor/ultra_grumpkin.hpp" +#include "barretenberg/honk/flavor/ultra_recursive.hpp" +#include "barretenberg/honk/sumcheck/sumcheck.hpp" +#include "barretenberg/plonk/proof_system/types/proof.hpp" +#include "barretenberg/stdlib/recursion/honk/transcript/transcript.hpp" + +namespace proof_system::plonk::stdlib::recursion::honk { +template class UltraRecursiveVerifier_ { + using FF = typename Flavor::FF; + using Commitment = typename Flavor::Commitment; + using VerificationKey = typename Flavor::VerificationKey; + using VerifierCommitmentKey = typename Flavor::VerifierCommitmentKey; + using Builder = typename Flavor::CircuitBuilder; + using PairingPoints = std::array; + + public: + explicit UltraRecursiveVerifier_(Builder* builder, std::shared_ptr verifier_key = nullptr); + UltraRecursiveVerifier_(UltraRecursiveVerifier_&& other) noexcept; + UltraRecursiveVerifier_(const UltraRecursiveVerifier_& other) = delete; + UltraRecursiveVerifier_& operator=(const UltraRecursiveVerifier_& other) = delete; + UltraRecursiveVerifier_& operator=(UltraRecursiveVerifier_&& other) noexcept; + + // TODO(luke): Eventually this will return something like aggregation_state but I'm simplifying for now until we + // determine the exact interface. Simply returns the two pairing points. + PairingPoints verify_proof(const plonk::proof& proof); + + std::shared_ptr key; + std::map commitments; + std::map pcs_fr_elements; + std::shared_ptr pcs_verification_key; + Builder* builder; + Transcript transcript; +}; + +extern template class UltraRecursiveVerifier_; + +using UltraRecursiveVerifier = UltraRecursiveVerifier_; + +} // namespace proof_system::plonk::stdlib::recursion::honk diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/verifier/verifier.test.cpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/verifier/verifier.test.cpp new file mode 100644 index 00000000000..ed62c4efa81 --- /dev/null +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/verifier/verifier.test.cpp @@ -0,0 +1,242 @@ +#include "barretenberg/honk/flavor/ultra_recursive.hpp" +#include "barretenberg/honk/proof_system/ultra_verifier.hpp" + +#include "barretenberg/common/test.hpp" +#include "barretenberg/ecc/curves/bn254/fq12.hpp" +#include "barretenberg/ecc/curves/bn254/pairing.hpp" +#include "barretenberg/honk/composer/ultra_composer.hpp" +#include "barretenberg/plonk/proof_system/proving_key/serialize.hpp" +#include "barretenberg/stdlib/hash/blake3s/blake3s.hpp" +#include "barretenberg/stdlib/hash/pedersen/pedersen.hpp" +#include "barretenberg/stdlib/primitives/curves/bn254.hpp" +#include "barretenberg/stdlib/recursion/honk/verifier/ultra_recursive_verifier.hpp" +#include "barretenberg/stdlib/recursion/verification_key/verification_key.hpp" +#include "barretenberg/transcript/transcript.hpp" + +namespace proof_system::plonk::stdlib::recursion::honk { + +template class RecursiveVerifierTest : public testing::Test { + + using InnerComposer = ::proof_system::honk::UltraComposer; + using InnerBuilder = typename InnerComposer::CircuitBuilder; + + using OuterBuilder = typename OuterComposer::CircuitBuilder; + + using NativeVerifier = ::proof_system::honk::UltraVerifier_<::proof_system::honk::flavor::Ultra>; + using RecursiveVerifier = UltraRecursiveVerifier_<::proof_system::honk::flavor::UltraRecursive>; + using VerificationKey = ::proof_system::honk::flavor::UltraRecursive::VerificationKey; + + using inner_curve = bn254; + using inner_scalar_field_ct = inner_curve::ScalarField; + using inner_ground_field_ct = inner_curve::BaseField; + using public_witness_ct = inner_curve::public_witness_ct; + using witness_ct = inner_curve::witness_ct; + using byte_array_ct = inner_curve::byte_array_ct; + + using inner_scalar_field = typename inner_curve::ScalarFieldNative; + + /** + * @brief Create an inner circuit, the proof of which will be recursively verified + * + * @param builder + * @param public_inputs + * @param log_num_gates + */ + static void create_inner_circuit(InnerBuilder& builder, + const std::vector& public_inputs, + size_t log_num_gates = 10) + { + // Create 2^log_n many add gates based on input log num gates + const size_t num_gates = 1 << log_num_gates; + for (size_t i = 0; i < num_gates; ++i) { + fr a = fr::random_element(); + uint32_t a_idx = builder.add_variable(a); + + fr b = fr::random_element(); + fr c = fr::random_element(); + fr d = a + b + c; + uint32_t b_idx = builder.add_variable(b); + uint32_t c_idx = builder.add_variable(c); + uint32_t d_idx = builder.add_variable(d); + + builder.create_big_add_gate({ a_idx, b_idx, c_idx, d_idx, fr(1), fr(1), fr(1), fr(-1), fr(0) }); + } + + // Create some additional "circuity" gates as an example + inner_scalar_field_ct a(public_witness_ct(&builder, public_inputs[0])); + inner_scalar_field_ct b(public_witness_ct(&builder, public_inputs[1])); + inner_scalar_field_ct c(public_witness_ct(&builder, public_inputs[2])); + + for (size_t i = 0; i < 32; ++i) { + a = (a * b) + b + a; + a = a.madd(b, c); + } + pedersen_commitment::compress(a, b); + byte_array_ct to_hash(&builder, "nonsense test data"); + blake3s(to_hash); + + inner_scalar_field bigfield_data = fr::random_element(); + inner_scalar_field bigfield_data_a{ bigfield_data.data[0], bigfield_data.data[1], 0, 0 }; + inner_scalar_field bigfield_data_b{ bigfield_data.data[2], bigfield_data.data[3], 0, 0 }; + + inner_ground_field_ct big_a(inner_scalar_field_ct(witness_ct(&builder, bigfield_data_a.to_montgomery_form())), + inner_scalar_field_ct(witness_ct(&builder, 0))); + inner_ground_field_ct big_b(inner_scalar_field_ct(witness_ct(&builder, bigfield_data_b.to_montgomery_form())), + inner_scalar_field_ct(witness_ct(&builder, 0))); + + big_a* big_b; + }; + + /** + * @brief Create a recursive verifier circuit and perform some native consistency checks + * @details Given an arbitrary inner circuit, construct a proof then consturct a recursive verifier circuit for that + * proof using the provided outer circuit builder. + * @note: The output of recursive verification is the two points which could be used in a pairing to do final + * verification. As a consistency check, we check that the outcome of performing this pairing (natively, no + * constraints) matches the outcome of running the full native verifier. + * + * @param inner_circuit Builder of the circuit for which a proof is recursively verified + * @param outer_builder Builder for the recursive verifier circuit + */ + static void create_outer_circuit(InnerBuilder& inner_circuit, OuterBuilder& outer_builder) + { + // Create proof of inner circuit + InnerComposer inner_composer; + auto prover = inner_composer.create_prover(inner_circuit); + auto proof_to_recursively_verify = prover.construct_proof(); + + info("Inner circuit size = ", prover.key->circuit_size); + + // Compute native verification key + const auto native_verification_key = inner_composer.compute_verification_key(inner_circuit); + + // Instantiate the recursive verification key from the native verification key + auto verification_key = std::make_shared(&outer_builder, native_verification_key); + + // Instantiate the recursive verifier and construct the recusive verification circuit + RecursiveVerifier verifier(&outer_builder, verification_key); + auto pairing_points = verifier.verify_proof(proof_to_recursively_verify); + + // For testing purposes only, perform native verification and compare the result + auto native_verifier = inner_composer.create_verifier(inner_circuit); + auto native_result = native_verifier.verify_proof(proof_to_recursively_verify); + + // Extract the pairing points from the recursive verifier output and perform the pairing natively. The result + // should match that of native verification. + auto lhs = pairing_points[0].get_value(); + auto rhs = pairing_points[1].get_value(); + auto recursive_result = native_verifier.pcs_verification_key->pairing_check(lhs, rhs); + EXPECT_EQ(recursive_result, native_result); + + // Confirm that the manifests produced by the recursive and native verifiers agree + auto recursive_manifest = verifier.transcript.get_manifest(); + auto native_manifest = native_verifier.transcript.get_manifest(); + // recursive_manifest.print(); + // native_manifest.print(); + for (size_t i = 0; i < recursive_manifest.size(); ++i) { + EXPECT_EQ(recursive_manifest[i], native_manifest[i]); + } + }; + + public: + static void SetUpTestSuite() { barretenberg::srs::init_crs_factory("../srs_db/ignition"); } + + /** + * @brief Create inner circuit and call check_circuit on it + * + */ + static void test_inner_circuit() + { + InnerBuilder builder; + std::vector inputs{ inner_scalar_field::random_element(), + inner_scalar_field::random_element(), + inner_scalar_field::random_element() }; + + create_inner_circuit(builder, inputs); + + bool result = builder.check_circuit(); + EXPECT_EQ(result, true); + } + + /** + * @brief Instantiate a recursive verification key from the native verification key produced by the inner cicuit + * builder. Check consistency beteen the native and stdlib types. + * + */ + static void test_recursive_verification_key_creation() + { + InnerBuilder inner_circuit; + OuterBuilder outer_circuit; + + std::vector inner_public_inputs{ inner_scalar_field::random_element(), + inner_scalar_field::random_element(), + inner_scalar_field::random_element() }; + + // Create an arbitrary inner circuit + create_inner_circuit(inner_circuit, inner_public_inputs); + + // Compute native verification key + InnerComposer inner_composer; + auto prover = inner_composer.create_prover(inner_circuit); // A prerequisite for computing VK + const auto native_verification_key = inner_composer.compute_verification_key(inner_circuit); + + // Instantiate the recursive verification key from the native verification key + auto verification_key = std::make_shared(&outer_circuit, native_verification_key); + + // Spot check some values in the recursive VK to ensure it was constructed correctly + EXPECT_EQ(verification_key->circuit_size, native_verification_key->circuit_size); + EXPECT_EQ(verification_key->log_circuit_size, native_verification_key->log_circuit_size); + EXPECT_EQ(verification_key->num_public_inputs, native_verification_key->num_public_inputs); + EXPECT_EQ(verification_key->q_m.get_value(), native_verification_key->q_m); + EXPECT_EQ(verification_key->q_r.get_value(), native_verification_key->q_r); + EXPECT_EQ(verification_key->sigma_1.get_value(), native_verification_key->sigma_1); + EXPECT_EQ(verification_key->id_3.get_value(), native_verification_key->id_3); + } + + /** + * @brief Construct a recursive verification circuit for the proof of an inner circuit then call check_circuit on it + * + */ + static void test_recursive_proof_composition() + { + InnerBuilder inner_circuit; + OuterBuilder outer_circuit; + + std::vector inner_public_inputs{ inner_scalar_field::random_element(), + inner_scalar_field::random_element(), + inner_scalar_field::random_element() }; + + // Create an arbitrary inner circuit + create_inner_circuit(inner_circuit, inner_public_inputs); + + // Create a recursive verification circuit for the proof of the inner circuit + create_outer_circuit(inner_circuit, outer_circuit); + + if (outer_circuit.failed()) { + info(outer_circuit.err()); + } + EXPECT_EQ(outer_circuit.failed(), false); + EXPECT_TRUE(outer_circuit.check_circuit()); + } +}; + +using OuterCircuitTypes = testing::Types<::proof_system::honk::UltraComposer>; + +TYPED_TEST_SUITE(RecursiveVerifierTest, OuterCircuitTypes); + +HEAVY_TYPED_TEST(RecursiveVerifierTest, InnerCircuit) +{ + TestFixture::test_inner_circuit(); +} + +HEAVY_TYPED_TEST(RecursiveVerifierTest, RecursiveVerificationKey) +{ + TestFixture::test_recursive_verification_key_creation(); +} + +HEAVY_TYPED_TEST(RecursiveVerifierTest, RecursiveProofComposition) +{ + TestFixture::test_recursive_proof_composition(); +}; + +} // namespace proof_system::plonk::stdlib::recursion::honk \ No newline at end of file diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/utility/utility.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/utility/utility.hpp index c7bca963fc4..0031ef3231a 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/utility/utility.hpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/utility/utility.hpp @@ -24,12 +24,12 @@ namespace proof_system::plonk::stdlib::recursion::utility { */ template class StdlibTypesUtility { using field_ct = field_t; - using witness_ct = witness_t; using fq_ct = bigfield; using element_ct = element; using FF = barretenberg::fr; using Commitment = barretenberg::g1::affine_element; template using Univariate = proof_system::honk::sumcheck::Univariate; + template using Univariate_ct = proof_system::honk::sumcheck::Univariate; public: /** @@ -82,18 +82,53 @@ template class StdlibTypesUtility { /** * @brief Construct field_t array from native Univariate type - * TODO(luke): do we need a stdlib Univariate or is std::array good enough? + * TODO(luke): do we need a stdlib Univariate or is Univariate good enough? * @param native_element - * @return std::array + * @return Univariate */ template - static std::array from_witness(Builder* builder, Univariate native_element) + static Univariate_ct from_witness(Builder* builder, Univariate native_element) { - std::array element; + Univariate_ct element; for (size_t i = 0; i < LENGTH; ++i) { - element[i] = field_ct::from_witness(builder, native_element.value_at(i)); + element.value_at(i) = field_ct::from_witness(builder, native_element.value_at(i)); } return element; } + + /** + * @brief Utility for mapping template parameter for recursive honk transcript deserialization to the + * corresponding template parameter for native honk transcipt deserialization. + * @details Data is extracted from a honk verfier transcript via a function of the form + * receive_from_prover(label). For the recursive transcript, T is generally a stdlib type or a container of + * stdlib types (e.g. Univariate). This struct and its specializations define the map T -> T_native, where + * T_native is the type extracted from the native transcript internal to the recursive transcipt. + * + * @tparam T + * @tparam LENGTH (used only for containers which specify a length, e.g. array/Univariate) + */ + template struct NativeType { + using type = void; + }; + + template struct NativeType { + using type = uint32_t; + }; + + template struct NativeType { + using type = FF; + }; + + template struct NativeType { + using type = Commitment; + }; + + template struct NativeType, 0> { + using type = std::array; + }; + + template struct NativeType, 0> { + using type = Univariate; + }; }; } // namespace proof_system::plonk::stdlib::recursion::utility \ No newline at end of file