Skip to content

Commit

Permalink
refactor: template Zeromorph by PCS (#5215)
Browse files Browse the repository at this point in the history
First step towards making Zeromorph work with both IPA and KZG, this PR
ensures that Zeromorph calls a univariate PCS building block for the
opening proof and verification, right now only instantiated with KZG.
  • Loading branch information
maramihali committed Mar 15, 2024
1 parent d8b8456 commit 03feab2
Show file tree
Hide file tree
Showing 21 changed files with 111 additions and 119 deletions.
13 changes: 9 additions & 4 deletions barretenberg/cpp/src/barretenberg/commitment_schemes/kzg/kzg.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@

namespace bb {

template <typename Curve> class KZG {
template <typename Curve_> class KZG {
public:
using Curve = Curve_;
using CK = CommitmentKey<Curve>;
using VK = VerifierCommitmentKey<Curve>;
using Fr = typename Curve::ScalarField;
using Commitment = typename Curve::AffineElement;
using GroupElement = typename Curve::Element;
using Polynomial = bb::Polynomial<Fr>;
using VerifierAccumulator = std::array<GroupElement, 2>;

/**
* @brief Computes the KZG commitment to an opening proof polynomial at a single evaluation point
Expand All @@ -27,7 +30,6 @@ template <typename Curve> class KZG {
* @param polynomial The witness whose opening proof needs to be computed
* @param prover_transcript Prover transcript
*/
public:
static void compute_opening_proof(std::shared_ptr<CK> ck,
const OpeningPair<Curve>& opening_pair,
const Polynomial& polynomial,
Expand Down Expand Up @@ -75,13 +77,16 @@ template <typename Curve> class KZG {
* - P₀ = C − v⋅[1]₁ + r⋅[W(x)]₁
* - P₁ = [W(x)]₁
*/
static std::array<GroupElement, 2> compute_pairing_points(const OpeningClaim<Curve>& claim,
const auto& verifier_transcript)
static VerifierAccumulator reduce_verify(const OpeningClaim<Curve>& claim, const auto& verifier_transcript)
{
auto quotient_commitment = verifier_transcript->template receive_from_prover<Commitment>("KZG:W");

// Note: The pairing check can be expressed naturally as
// e(C - v * [1]_1, [1]_2) = e([W]_1, [X - r]_2) where C =[p(X)]_1. This can be rearranged (e.g. see the plonk
// paper) as e(C + r*[W]_1 - v*[1]_1, [1]_2) * e(-[W]_1, [X]_2) = 1, or e(P_0, [1]_2) * e(P_1, [X]_2) = 1
GroupElement P_0;
if constexpr (Curve::is_stdlib_type) {
// Express operation as a batch_mul in order to use Goblinization if available
auto builder = quotient_commitment.get_context();
auto one = Fr(builder, 1);
std::vector<GroupElement> commitments = { claim.commitment,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ template <class FF> inline std::vector<FF> powers_of_challenge(const FF challeng
/**
* @brief Prover for ZeroMorph multilinear PCS
*
* @tparam Curve
* @tparam PCS - The univariate PCS used inside ZeroMorph as a building block
*/
template <typename Curve> class ZeroMorphProver_ {
template <typename PCS> class ZeroMorphProver_ {
using Curve = typename PCS::Curve;
using FF = typename Curve::ScalarField;
using Commitment = typename Curve::AffineElement;
using Polynomial = bb::Polynomial<FF>;
Expand Down Expand Up @@ -263,10 +264,14 @@ template <typename Curve> class ZeroMorphProver_ {
}

/**
* @brief Compute combined evaluation and degree-check quotient polynomial pi
* @details Compute univariate quotient pi, where
* @brief Compute combined evaluation and degree-check polynomial pi
* @details Compute univariate polynomial pi, where
*
* pi = (\zeta_c + z*Z_x) X^{N_{max}-(N-1)}
*
* pi = (q_\zeta + z*q_Z) X^{N_{max}-(N-1)}, with q_\zeta = \zeta_x/(X-x), q_Z = Z_x/(X-x)
* The proof that pi(x) = 0 for some verifier challenge x will then be computed as part of the univariate PCS
* opening. If this is instantiated with KZG, the PCS is going to compute the quotient
* q_pi = (q_\zeta + z*q_Z)X^{N_{max}-(N-1)}, with q_\zeta = \zeta_x/(X-x), q_Z = Z_x/(X-x),
*
* @param Z_x
* @param zeta_x
Expand All @@ -275,35 +280,30 @@ template <typename Curve> class ZeroMorphProver_ {
* @param N_max
* @return Polynomial
*/
static Polynomial compute_batched_evaluation_and_degree_check_quotient(Polynomial& zeta_x,
Polynomial& Z_x,
FF x_challenge,
FF z_challenge)
static Polynomial compute_batched_evaluation_and_degree_check_polynomial(Polynomial& zeta_x,
Polynomial& Z_x,
FF z_challenge)
{
// We cannot commit to polynomials with size > N_max
size_t N = zeta_x.size();
ASSERT(N <= N_max);

// Compute q_{\zeta} and q_Z in place
zeta_x.factor_roots(x_challenge);
Z_x.factor_roots(x_challenge);

// Compute batched quotient q_{\zeta} + z*q_Z
auto batched_quotient = zeta_x;
batched_quotient.add_scaled(Z_x, z_challenge);

// TODO(#742): To complete the degree check, we need to commit to (q_{\zeta} + z*q_Z)*X^{N_max - N - 1}.
// Verification then requires a pairing check similar to the standard KZG check but with [1]_2 replaced by
// [X^{N_max - N -1}]_2. Two issues: A) we do not have an SRS with these G2 elements (so need to generate a fake
// setup until we can do the real thing), and B) its not clear to me how to update our pairing algorithms to do
// this type of pairing. For now, simply construct q_{\zeta} + z*q_Z without the shift and do a standard KZG
// pairing check. When we're ready, all we have to do to make this fully legit is commit to the shift here and
// update the pairing check accordingly. Note: When this is implemented properly, it doesnt make sense to store
// the (massive) shifted polynomial of size N_max. Ideally would only store the unshifted version and just
// compute the shifted commitment directly via a new method.
auto batched_shifted_quotient = batched_quotient;

return batched_shifted_quotient;
// Compute batched polynomial zeta_x + Z_x
auto batched_polynomial = zeta_x;
batched_polynomial.add_scaled(Z_x, z_challenge);

// TODO(#742): To complete the degree check, we need to do an opening proof for x_challenge with a univariate
// PCS for the degree-lifted polynomial (\zeta_c + z*Z_x)*X^{N_max - N - 1}. If this PCS is KZG, verification
// then requires a pairing check similar to the standard KZG check but with [1]_2 replaced by [X^{N_max - N
// -1}]_2. Two issues: A) we do not have an SRS with these G2 elements (so need to generate a fake setup until
// we can do the real thing), and B) its not clear to me how to update our pairing algorithms to do this type of
// pairing. For now, simply construct pi without the shift and do a standard KZG pairing check if the PCS is
// KZG. When we're ready, all we have to do to make this fully legit is commit to the shift here and update the
// pairing check accordingly. Note: When this is implemented properly, it doesnt make sense to store the
// (massive) shifted polynomial of size N_max. Ideally would only store the unshifted version and just compute
// the shifted commitment directly via a new method.

return batched_polynomial;
}

/**
Expand Down Expand Up @@ -424,12 +424,11 @@ template <typename Curve> class ZeroMorphProver_ {
concatenation_groups_batched);

// Compute batched degree-check and ZM-identity quotient polynomial pi
auto pi_polynomial =
compute_batched_evaluation_and_degree_check_quotient(zeta_x, Z_x, x_challenge, z_challenge);
auto pi_polynomial = compute_batched_evaluation_and_degree_check_polynomial(zeta_x, Z_x, z_challenge);

// Compute and send proof commitment pi
auto pi_commitment = commitment_key->commit(pi_polynomial);
transcript->send_to_verifier("ZM:PI", pi_commitment);
// Compute opening proof for x_challenge using the underlying univariate PCS
PCS::compute_opening_proof(
commitment_key, { .challenge = x_challenge, .evaluation = FF(0) }, pi_polynomial, transcript);
}
};

Expand All @@ -438,9 +437,11 @@ template <typename Curve> class ZeroMorphProver_ {
*
* @tparam Curve
*/
template <typename Curve> class ZeroMorphVerifier_ {
template <typename PCS> class ZeroMorphVerifier_ {
using Curve = typename PCS::Curve;
using FF = typename Curve::ScalarField;
using Commitment = typename Curve::AffineElement;
using VerifierAccumulator = PCS::VerifierAccumulator;

public:
/**
Expand Down Expand Up @@ -634,15 +635,14 @@ template <typename Curve> class ZeroMorphVerifier_ {
* @param transcript
* @return std::array<Commitment, 2> Inputs to the final pairing check
*/
static std::array<Commitment, 2> verify(
RefSpan<Commitment> unshifted_commitments,
RefSpan<Commitment> to_be_shifted_commitments,
RefSpan<FF> unshifted_evaluations,
RefSpan<FF> shifted_evaluations,
std::span<FF> multivariate_challenge,
auto& transcript,
const std::vector<RefVector<Commitment>>& concatenation_group_commitments = {},
RefSpan<FF> concatenated_evaluations = {})
static VerifierAccumulator verify(RefSpan<Commitment> unshifted_commitments,
RefSpan<Commitment> to_be_shifted_commitments,
RefSpan<FF> unshifted_evaluations,
RefSpan<FF> shifted_evaluations,
std::span<FF> multivariate_challenge,
auto& transcript,
const std::vector<RefVector<Commitment>>& concatenation_group_commitments = {},
RefSpan<FF> concatenated_evaluations = {})
{
size_t log_N = multivariate_challenge.size();
FF rho = transcript->template get_challenge<FF>("rho");
Expand Down Expand Up @@ -696,36 +696,16 @@ template <typename Curve> class ZeroMorphVerifier_ {
Commitment C_zeta_Z;
if constexpr (Curve::is_stdlib_type) {
// Express operation as a batch_mul in order to use Goblinization if available
auto builder = rho.get_context();
auto builder = z_challenge.get_context();
std::vector<FF> scalars = { FF(builder, 1), z_challenge };
std::vector<Commitment> points = { C_zeta_x, C_Z_x };
C_zeta_Z = Commitment::batch_mul(points, scalars);
} else {
C_zeta_Z = C_zeta_x + C_Z_x * z_challenge;
}

// Receive proof commitment \pi
auto C_pi = transcript->template receive_from_prover<Commitment>("ZM:PI");

// Construct inputs and perform pairing check to verify claimed evaluation
// Note: The pairing check (without the degree check component X^{N_max-N-1}) can be expressed naturally as
// e(C_{\zeta,Z}, [1]_2) = e(pi, [X - x]_2). This can be rearranged (e.g. see the plonk paper) as
// e(C_{\zeta,Z} - x*pi, [1]_2) * e(-pi, [X]_2) = 1, or
// e(P_0, [1]_2) * e(P_1, [X]_2) = 1
Commitment P0;
if constexpr (Curve::is_stdlib_type) {
// Express operation as a batch_mul in order to use Goblinization if available
auto builder = rho.get_context();
std::vector<FF> scalars = { FF(builder, 1), x_challenge };
std::vector<Commitment> points = { C_zeta_Z, C_pi };
P0 = Commitment::batch_mul(points, scalars);
} else {
P0 = C_zeta_Z + C_pi * x_challenge;
}

auto P1 = -C_pi;

return { P0, P1 };
return PCS::reduce_verify(
{ .opening_pair = { .challenge = x_challenge, .evaluation = FF(0) }, .commitment = C_zeta_Z }, transcript);
}
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
#include "zeromorph.hpp"
#include "../commitment_key.test.hpp"
#include "barretenberg/commitment_schemes/kzg/kzg.hpp"
#include "barretenberg/transcript/transcript.hpp"

#include <gtest/gtest.h>

namespace bb {

template <class Curve> class ZeroMorphTest : public CommitmentTest<Curve> {
template <class PCS> class ZeroMorphTest : public CommitmentTest<typename PCS::Curve> {
public:
using Curve = typename PCS::Curve;
using Fr = typename Curve::ScalarField;
using Polynomial = bb::Polynomial<Fr>;
using Commitment = typename Curve::AffineElement;
using GroupElement = typename Curve::Element;
using ZeroMorphProver = ZeroMorphProver_<Curve>;
using ZeroMorphVerifier = ZeroMorphVerifier_<Curve>;
using ZeroMorphProver = ZeroMorphProver_<PCS>;
using ZeroMorphVerifier = ZeroMorphVerifier_<PCS>;

// Evaluate Phi_k(x) = \sum_{i=0}^k x^i using the direct inefficent formula
Fr Phi(Fr challenge, size_t subscript)
Expand Down Expand Up @@ -105,14 +107,15 @@ template <class Curve> class ZeroMorphTest : public CommitmentTest<Curve> {
}
};

template <class Curve> class ZeroMorphWithConcatenationTest : public CommitmentTest<Curve> {
template <class PCS> class ZeroMorphWithConcatenationTest : public CommitmentTest<typename PCS::Curve> {
public:
using Curve = typename PCS::Curve;
using Fr = typename Curve::ScalarField;
using Polynomial = bb::Polynomial<Fr>;
using Commitment = typename Curve::AffineElement;
using GroupElement = typename Curve::Element;
using ZeroMorphProver = ZeroMorphProver_<Curve>;
using ZeroMorphVerifier = ZeroMorphVerifier_<Curve>;
using ZeroMorphProver = ZeroMorphProver_<PCS>;
using ZeroMorphVerifier = ZeroMorphVerifier_<PCS>;

// Evaluate Phi_k(x) = \sum_{i=0}^k x^i using the direct inefficent formula
Fr Phi(Fr challenge, size_t subscript)
Expand Down Expand Up @@ -260,9 +263,9 @@ template <class Curve> class ZeroMorphWithConcatenationTest : public CommitmentT
}
};

using CurveTypes = ::testing::Types<curve::BN254>;
TYPED_TEST_SUITE(ZeroMorphTest, CurveTypes);
TYPED_TEST_SUITE(ZeroMorphWithConcatenationTest, CurveTypes);
using PCSTypes = ::testing::Types<KZG<curve::BN254>>;
TYPED_TEST_SUITE(ZeroMorphTest, PCSTypes);
TYPED_TEST_SUITE(ZeroMorphWithConcatenationTest, PCSTypes);

/**
* @brief Test method for computing q_k given multilinear f
Expand All @@ -276,7 +279,8 @@ TYPED_TEST(ZeroMorphTest, QuotientConstruction)
{
// Define some useful type aliases
using ZeroMorphProver = ZeroMorphProver_<TypeParam>;
using Fr = typename TypeParam::ScalarField;
using Curve = typename TypeParam::Curve;
using Fr = typename Curve::ScalarField;
using Polynomial = bb::Polynomial<Fr>;

// Define size parameters
Expand Down Expand Up @@ -323,7 +327,8 @@ TYPED_TEST(ZeroMorphTest, BatchedLiftedDegreeQuotient)
{
// Define some useful type aliases
using ZeroMorphProver = ZeroMorphProver_<TypeParam>;
using Fr = typename TypeParam::ScalarField;
using Curve = typename TypeParam::Curve;
using Fr = typename Curve::ScalarField;
using Polynomial = bb::Polynomial<Fr>;

const size_t N = 8;
Expand Down Expand Up @@ -367,7 +372,8 @@ TYPED_TEST(ZeroMorphTest, PartiallyEvaluatedQuotientZeta)
{
// Define some useful type aliases
using ZeroMorphProver = ZeroMorphProver_<TypeParam>;
using Fr = typename TypeParam::ScalarField;
using Curve = typename TypeParam::Curve;
using Fr = typename Curve::ScalarField;
using Polynomial = bb::Polynomial<Fr>;

const size_t N = 8;
Expand Down Expand Up @@ -409,7 +415,8 @@ TYPED_TEST(ZeroMorphTest, PartiallyEvaluatedQuotientZeta)
*/
TYPED_TEST(ZeroMorphTest, PhiEvaluation)
{
using Fr = typename TypeParam::ScalarField;
using Curve = typename TypeParam::Curve;
using Fr = typename Curve::ScalarField;
const size_t N = 8;
size_t n = numeric::get_msb(N);

Expand Down Expand Up @@ -449,7 +456,8 @@ TYPED_TEST(ZeroMorphTest, PartiallyEvaluatedQuotientZ)
{
// Define some useful type aliases
using ZeroMorphProver = ZeroMorphProver_<TypeParam>;
using Fr = typename TypeParam::ScalarField;
using Curve = typename TypeParam::Curve;
using Fr = typename Curve::ScalarField;
using Polynomial = bb::Polynomial<Fr>;

const size_t N = 8;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ template <typename BuilderType> class GoblinUltraRecursiveFlavor_ {
public:
using CircuitBuilder = BuilderType; // Determines arithmetization of circuit instantiated with this flavor
using Curve = stdlib::bn254<CircuitBuilder>;
using PCS = KZG<Curve>;
using GroupElement = typename Curve::Element;
using FF = typename Curve::ScalarField;
using Commitment = typename Curve::Element;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ template <typename BuilderType> class UltraRecursiveFlavor_ {
public:
using CircuitBuilder = BuilderType; // Determines arithmetization of circuit instantiated with this flavor
using Curve = stdlib::bn254<CircuitBuilder>;
using PCS = KZG<Curve>;
using GroupElement = typename Curve::Element;
using Commitment = typename Curve::Element;
using FF = typename Curve::ScalarField;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ template <IsUltraFlavor Flavor> class DeciderProver_ {
using Polynomial = typename Flavor::Polynomial;
using ProverPolynomials = typename Flavor::ProverPolynomials;
using CommitmentLabels = typename Flavor::CommitmentLabels;
using Curve = typename Flavor::Curve;
using PCS = typename Flavor::PCS;
using Instance = ProverInstance_<Flavor>;
using Transcript = typename Flavor::Transcript;
using RelationSeparator = typename Flavor::RelationSeparator;
Expand Down Expand Up @@ -47,7 +47,7 @@ template <IsUltraFlavor Flavor> class DeciderProver_ {

std::shared_ptr<CommitmentKey> commitment_key;

using ZeroMorph = ZeroMorphProver_<Curve>;
using ZeroMorph = ZeroMorphProver_<PCS>;

private:
HonkProof proof;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ DeciderVerifier_<Flavor>::DeciderVerifier_()
*/
template <typename Flavor> bool DeciderVerifier_<Flavor>::verify_proof(const HonkProof& proof)
{
using Curve = typename Flavor::Curve;
using ZeroMorph = ZeroMorphVerifier_<Curve>;
using PCS = typename Flavor::PCS;
using ZeroMorph = ZeroMorphVerifier_<PCS>;
using VerifierCommitments = typename Flavor::VerifierCommitments;

transcript = std::make_shared<Transcript>(proof);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ template <typename Flavor>
std::array<typename Flavor::GroupElement, 2> DeciderRecursiveVerifier_<Flavor>::verify_proof(const HonkProof& proof)
{
using Sumcheck = ::bb::SumcheckVerifier<Flavor>;
using Curve = typename Flavor::Curve;
using ZeroMorph = ::bb::ZeroMorphVerifier_<Curve>;
using PCS = typename Flavor::PCS;
using ZeroMorph = ::bb::ZeroMorphVerifier_<PCS>;
using VerifierCommitments = typename Flavor::VerifierCommitments;
using Transcript = typename Flavor::Transcript;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ std::array<typename bn254<CircuitBuilder>::Element, 2> MergeRecursiveVerifier_<C

OpeningClaim batched_claim = { { kappa, batched_eval }, batched_commitment };

auto pairing_points = KZG::compute_pairing_points(batched_claim, transcript);
auto pairing_points = KZG::reduce_verify(batched_claim, transcript);

return pairing_points;
}
Expand Down
Loading

0 comments on commit 03feab2

Please sign in to comment.