From 0e9a530cfd83f375f6b3a1bb9fb67cf562847f9b Mon Sep 17 00:00:00 2001 From: iakovenkos <105737703+iakovenkos@users.noreply.github.com> Date: Mon, 19 Aug 2024 13:55:34 +0200 Subject: [PATCH] feat: zk sumcheck (#7517) Added ZK Sumcheck that ensures that neither round univariates nor claimed evaluations leak witness information ZK Sumcheck is "togglable": only Flavors with (HasZK = true) use it Refactored sumcheck tests: now they are typed by the Flavor (Ultra or UltraWithZK) Made sumcheck-outline.md consistent with the implementation, expanded docs in sumcheck.hpp and sumcheck_round.hpp Note: ultra/mega/... -provers and verifiers using ZK sumcheck will be added later Closes https://github.com/AztecProtocol/barretenberg/issues/979 --- barretenberg/cpp/docs/Doxyfile | 2 +- barretenberg/cpp/docs/src/sumcheck-outline.md | 229 +++++----- .../cpp/src/barretenberg/flavor/flavor.hpp | 2 + .../stdlib_circuit_builders/ultra_keccak.hpp | 4 + .../src/barretenberg/sumcheck/sumcheck.hpp | 417 ++++++++++++++++-- .../barretenberg/sumcheck/sumcheck.test.cpp | 400 ++++++++++------- .../barretenberg/sumcheck/sumcheck_output.hpp | 25 +- .../barretenberg/sumcheck/sumcheck_round.hpp | 108 ++++- .../sumcheck/zk_sumcheck_data.hpp | 53 +++ 9 files changed, 932 insertions(+), 308 deletions(-) create mode 100644 barretenberg/cpp/src/barretenberg/sumcheck/zk_sumcheck_data.hpp diff --git a/barretenberg/cpp/docs/Doxyfile b/barretenberg/cpp/docs/Doxyfile index 9727453b25c..2bdf7d08b70 100644 --- a/barretenberg/cpp/docs/Doxyfile +++ b/barretenberg/cpp/docs/Doxyfile @@ -1355,7 +1355,7 @@ HTML_EXTRA_FILES = # The default value is: AUTO_LIGHT. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_COLORSTYLE = AUTO_LIGHT +HTML_COLORSTYLE = TOGGLE # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to diff --git a/barretenberg/cpp/docs/src/sumcheck-outline.md b/barretenberg/cpp/docs/src/sumcheck-outline.md index 6a78eddad0b..c141e96446e 100644 --- a/barretenberg/cpp/docs/src/sumcheck-outline.md +++ b/barretenberg/cpp/docs/src/sumcheck-outline.md @@ -24,11 +24,11 @@ The implementation consists of several components. ### Sumcheck Relation {#SumcheckRelation} -Given multilinear polynomials \f$ P*1,\ldots, P_N \in \mathbb{F}[X_0,\ldots, X*{d-1}] \f$ and a polynomial \f$ F \f$ in \f$ N \f$ variables, we run Sumcheck over the polynomial +Given multilinear polynomials \f$ P_1,\ldots, P_N \in \mathbb{F}[X_0,\ldots, X_{d-1}] \f$ and a polynomial \f$ F \f$ in \f$ N \f$ variables, we run Sumcheck over the polynomial \f{align}{ \tilde{F} -(X*0,\ldots, X*{d-1}) = -pow*{\beta}(X_0,\ldots, X*{d-1}) \cdot F\left( P*1 (X_0,\ldots, X*{d-1}), \ldots, P*N (X_0,\ldots, X*{d-1}) \right) +(X_0,\ldots, X_{d-1}) = +pow_{\beta}(X_0,\ldots, X_{d-1}) \cdot F\left( P_1 (X_0,\ldots, X_{d-1}), \ldots, P_N (X_0,\ldots, X_{d-1}) \right) \f} to establish that \f$ F(P_1(\vec \ell),\ldots, P_N(\vec \ell) ) = 0 \f$, i.e. that \f$ F \f$ is satisfied at every point \f$\vec \ell \{0,1\}^d\f$. @@ -55,9 +55,9 @@ The following constants are used in this exposition. The significance of this parameter becomes apparent in Section [Masking Evaluations of Multilinear Witnesses](#MaskingEvalsOfWitnesses). It is formally defined as follows \f{align}{ -D*w = \deg*{P*1, \ldots, P*{N*w}} F(P_1,\ldots, P*{N}) +D_w = \deg_{P_1, \ldots, P_{N_w}} F(P_1,\ldots, P_{N}) \f} -where by \f$ \deg*{P_1, \ldots, P*{N*w}} \f$ we mean the total degree of the relation polynomial \f$ F \f$ in the witness polynomials \f$ P_1,\ldots, P*{N_w}\f$ considered as variables. +where by \f$ \deg_{P_1, \ldots, P_{N_w}} \f$ we mean the total degree of the relation polynomial \f$ F \f$ in the witness polynomials \f$ P_1,\ldots, P_{N_w}\f$ considered as variables. For example, given a polynomial \f$P_1 + P_{N_w+1} \cdot P_{N_w + 2} \cdot P_{1}^2 \cdot P_{2}\f$ in prover polynomials, where \f$N_w>2\f$, its witness degree \f$ D_w \f$ is \f$3\f$, whereas its total degree \f$D\f$ is equal to \f$ 6 \f$. @@ -78,18 +78,18 @@ Sumcheck Prover algorithm takes a reference to an object of this class. The prover evaluates the round univariate \f{align}{ -\tilde{S}^i = \sum*{\vec \ell \in \{0,1\}^{d-1-i}} \tilde{F}\left(P_1(u_0,\ldots, u*{i-1}, X*i,\vec \ell), \ldots, P_N(u_0,\ldots, u*{i-1}, X*i,\vec \ell)\right) +\tilde{S}^i = \sum_{\vec \ell \in \{0,1\}^{d-1-i}} \tilde{F}\left(P_1(u_0,\ldots, u_{i-1}, X_i,\vec \ell), \ldots, P_N(u_0,\ldots, u_{i-1}, X_i,\vec \ell)\right) \f} -over the domain \f$ 0,\ldots, D \f$. In fact, it is more efficient to perform this computation sub-relation-wise, because the degrees of individual subrelations as polynomials in \f$ P_1,\ldots, P_N\f$ are generally smaller than \f$D\f$ defined in [Main Parameters](#MainParameters). Taking this into account, for a given subrelation of \f$F\f$, we perform expensive subrelation evaluations at points \f$(u_0,\ldots, u*{i-1}, k, \vec \ell)\f$ for \f$\ell \in \{0,1\}^{d-1-i} \f$ and \f$k\f$ from \f$0\f$ only up to the degree of the subrelation as a polynomial in \f$P_1,\ldots,P_N\f$ incremented by \f$1\f$. +over the domain \f$ 0,\ldots, D \f$. In fact, it is more efficient to perform this computation sub-relation-wise, because the degrees of individual subrelations as polynomials in \f$ P_1,\ldots, P_N\f$ are generally smaller than \f$D\f$ defined in [Main Parameters](#MainParameters). Taking this into account, for a given subrelation of \f$F\f$, we perform expensive subrelation evaluations at points \f$(u_0,\ldots, u_{i-1}, k, \vec \ell)\f$ for \f$\ell \in \{0,1\}^{d-1-i} \f$ and \f$k\f$ from \f$0\f$ only up to the degree of the subrelation as a polynomial in \f$P_1,\ldots,P_N\f$ incremented by \f$1\f$. At the implementation level, the evaluations of \f$\tilde{S}^i\f$ are obtained using the method \ref bb::SumcheckProverRound< Flavor >::compute_univariate "compute univariate" consisting of the following sub-methods: -- \ref bb::SumcheckProverRound::extend*edges "Extend evaluations" of linear univariate - polynomials \f$ P_j(u_0,\ldots, u*{i-1}, X_i, \vec \ell) \f$ to the domain \f$0,\ldots, D\f$. It is a cheap operation applied only once for every \f$\vec \ell \in \{0,1\}^d\f$ which allows to compute subrelations of \f$ F \f$ at such arguments. +- \ref bb::SumcheckProverRound::extend_edges "Extend evaluations" of linear univariate + polynomials \f$ P_j(u_0,\ldots, u_{i-1}, X_i, \vec \ell) \f$ to the domain \f$0,\ldots, D\f$. It is a cheap operation applied only once for every \f$\vec \ell \in \{0,1\}^d\f$ which allows to compute subrelations of \f$ F \f$ at such arguments. - \ref bb::SumcheckProverRound::accumulate_relation_univariates "Accumulate per-relation contributions" of the extended polynomials to auxiliary univariates \f$ T^i(X_i)\f$ defined in \ref SumcheckProverContributionsofPow "this section" -- \ref bb::SumcheckProverRound::extend*and_batch_univariates "Extend and batch the subrelation contributions" - multiplying by the constants \f$c_i\f$ and the evaluations of \f$ ( (1−X_i) + X_i\cdot \beta_i ) \f$ stemming from \f$F\f$ being multiplied by \f$pow*{\beta}\f$. +- \ref bb::SumcheckProverRound::extend_and_batch_univariates "Extend and batch the subrelation contributions" + multiplying by the constants \f$c_i\f$ and the evaluations of \f$ ( (1−X_i) + X_i\cdot \beta_i ) \f$ stemming from \f$F\f$ being multiplied by \f$pow_{\beta}\f$. #### Get Round Challenge {#GetRoundChallenge} @@ -97,22 +97,22 @@ After computing Round Univariate and adding its evaluations \f$\tilde{S}^i(0),\l #### Populate/Update Book-keeping Table {#BookKeepingTable} -To keep prover's work linear in the number of coefficients of \f$P_1,\ldots, P_N\f$, we \ref bb::SumcheckProver< Flavor >::partially*evaluate "populate" a table of \f$\texttt{partially_evaluated_polynomials}\f$ after getting the first challenge \f$ u_0 \f$ with the values \f$P_j(u_0,\vec \ell )\f$, namely +To keep prover's work linear in the number of coefficients of \f$P_1,\ldots, P_N\f$, we \ref bb::SumcheckProver< Flavor >::partially_evaluate "populate" a table of \f$\texttt{partially_evaluated_polynomials}\f$ after getting the first challenge \f$ u_0 \f$ with the values \f$P_j(u_0,\vec \ell )\f$, namely \f{align}{ -\texttt{partially_evaluated_polynomials}*{\ell,j} \gets P*j(0, \ell) + u*{0} \cdot \left(P_j(1, \vec \ell) - P_j(0, \ell)\right) \f} +\texttt{partially_evaluated_polynomials}_{\ell,j} \gets P_j(0, \ell) + u_{0} \cdot \left(P_j(1, \vec \ell) - P_j(0, \ell)\right) \f} for \f$ \vec \ell \in \{0,1\}^{d-1}\f$ identified with the binary representation of \f$ 0\leq \ell \leq 2^{d-1}-1\f$. -In Round \f$0< i \leq d-1\f$, the prover algorithm \ref bb::SumcheckProver< Flavor >::partially*evaluate "updates" the top \f$ 2^{d-1 - i}\f$ values in the book-keeping table +In Round \f$0< i \leq d-1\f$, the prover algorithm \ref bb::SumcheckProver< Flavor >::partially_evaluate "updates" the top \f$ 2^{d-1 - i}\f$ values in the book-keeping table \f{align}{ -\texttt{partially_evaluated_polynomials}*{\ell,j} \gets \texttt{partially*evaluated_polynomials}*{2 \ell,j} + u*{i} \cdot (\texttt{partially_evaluated_polynomials}*{2\ell+1,j} - \texttt{partially*evaluated_polynomials}*{2\ell,j}) \f} +\texttt{partially_evaluated_polynomials}_{\ell,j} \gets \texttt{partially_evaluated_polynomials}_{2 \ell,j} + u_{i} \cdot (\texttt{partially_evaluated_polynomials}_{2\ell+1,j} - \texttt{partially_evaluated_polynomials}_{2\ell,j}) \f} where \f$\vec \ell \in \{0,1\}^{d-1-i}\f$. -After the final update, i.e. when \f$ i = d-1 \f$, the upper row of the table contains the evaluations of Prover Polynomials at the challenge point \f$ (u*0,\ldots, u*{d-1}) \f$. +After the final update, i.e. when \f$ i = d-1 \f$, the upper row of the table contains the evaluations of Prover Polynomials at the challenge point \f$ (u_0,\ldots, u_{d-1}) \f$. #### Add Claimed Evaluations to Transcript {#ClaimedEvaluations} -After computing the last challenge \f$ u*{d-1} \f$ in Round \f$ d-1 \f$ and updating \f$ +After computing the last challenge \f$ u_{d-1} \f$ in Round \f$ d-1 \f$ and updating \f$ \texttt{partially_evaluated_polynomials} \f$, the prover looks into the top row of the table containing evaluations -\f$P_1(u_0,\ldots, u*{d-1}), \ldots, P*N(u_0,\ldots, u*{d-1})\f$ and concatenates these values with the last challenge +\f$P_1(u_0,\ldots, u_{d-1}), \ldots, P_N(u_0,\ldots, u_{d-1})\f$ and concatenates these values with the last challenge to the transcript. ## Sumcheck Verifier Algorithm {#NonZKSumcheckVerifier} @@ -126,11 +126,11 @@ The verifier's work reduces to the following. For \f$ i = 0,\ldots, d-1\f$: - Using \ref bb::BaseTranscript::receive_from_prover "receive_from_prover" method from \ref bb::BaseTranscript< TranscriptParams > "Base Transcript Class", extract the evaluations of Round Univariate \f$ \tilde{S}^i(0),\ldots, \tilde{S}^i(D) \f$ from the transcript. -- \ref bb::SumcheckVerifierRound< Flavor >::check*sum "Check target sum": \f$\quad \sigma*{ +- \ref bb::SumcheckVerifierRound< Flavor >::check_sum "Check target sum": \f$\quad \sigma_{ i } \stackrel{?}{=} \tilde{S}^i(0) + \tilde{S}^i(1) \f$. - \ref bb::BaseTranscript::get_challenge "Get the next challenge" \f$u_i\f$ by hashing the transcript. method. -- \ref bb::SumcheckVerifierRound< Flavor >::compute*next_target_sum "Compute next target sum" :\f$ \quad \sigma*{i+1} +- \ref bb::SumcheckVerifierRound< Flavor >::compute_next_target_sum "Compute next target sum" :\f$ \quad \sigma_{i+1} \gets \tilde{S}^i(u_i) \f$ ### Verifier's Data before Final Step {#SumcheckVerifierData} @@ -142,13 +142,13 @@ and computed \f$\sigma_d = \tilde{S}^{d-1}(u_{d-1})\f$. ### Final Verification Step {#NonZKSumcheckVerification} - Extract claimed evaluations of prover polynomials \f$P_1,\ldots, P_N\f$ at the challenge point \f$ - (u_0,\ldots,u_{d-1}) \f$ from the transcript and \ref bb::SumcheckVerifierRound< Flavor >::compute*full_honk_relation_purported_value "compute evaluation:" - \f{align}{\tilde{F}\left( P_1(u_0,\ldots, u*{d-1}), \ldots, P*N(u_0,\ldots, u*{d-1}) \right)\f} + (u_0,\ldots,u_{d-1}) \f$ from the transcript and \ref bb::SumcheckVerifierRound< Flavor >::compute_full_honk_relation_purported_value "compute evaluation:" + \f{align}{\tilde{F}\left( P_1(u_0,\ldots, u_{d-1}), \ldots, P_N(u_0,\ldots, u_{d-1}) \right)\f} -- Compare \f$ \sigma*d \f$ against the evaluation of \f$ \tilde{F} \f$ at \f$P_1(u_0,\ldots, u*{d-1}), \ldots, - P*N(u_0,\ldots, u*{d-1})\f$: - \f{align}{\quad \sigma*{ d } \stackrel{?}{=} \tilde{F}\left(P_1(u*{0}, \ldots, u*{d-1}),\ldots, P_N(u_0,\ldots, - u*{d-1})\right)\f} +- Compare \f$ \sigma_d \f$ against the evaluation of \f$ \tilde{F} \f$ at \f$P_1(u_0,\ldots, u_{d-1}), \ldots, + P_N(u_0,\ldots, u_{d-1})\f$: + \f{align}{\quad \sigma_{ d } \stackrel{?}{=} \tilde{F}\left(P_1(u_{0}, \ldots, u_{d-1}),\ldots, P_N(u_0,\ldots, + u_{d-1})\right)\f} ## Witness Information Leakage {#NonZKSumcheckLeakage} @@ -159,129 +159,144 @@ As explained in Section 13.3 of ::setup_zk_sumcheck_data "setup_zk_sumcheck_data", which also adds the Libra sum to the transcript. ### Pre-computed Data and Book-Keeping {#LibraBookKeeping} - -As in [Sumcheck Book-keeping](#BookKeepingTable), we use a table of evaluations of Libra univariates to avoid extra computational costs. -Namely, before Round \f$ i \f$, the prover needs the table of values +As in [Sumcheck Book-keeping](#BookKeepingTable), we use a table of evaluations of Libra univariates that is being updated in each round. +Namely, before entering the first round, the prover updates the vector of Libra univariates in place \f{align}{ -\texttt{libra*table}*{j,k} \gets \rho \cdot 2^{d-1-i} \cdot g*{j,k} \text{ for } j= i,\ldots, d-1, \text{ and } k=0,\ldots, \tilde{D} -\f} -and the term + \texttt{libra_univariates}_{j}(k) \gets \texttt{libra_challenge} \cdot 2^{d-1} \cdot g_{j}(k) \text{ for } j= i,\ldots, d-1, \text{ and } k=0,\ldots, \tilde{D} +\f} +and computes the term \f{align}{ -\texttt{libra_running_sum} \gets \rho \cdot 2^{d-1-i}\left( \sum*{j=0}^{i-1}g*j(u_j) + \sum*{j = i+1}^{d-1} ( g*{j,0} + g*{j,1}) \right). + \texttt{libra_running_sum} \gets 2^{-1} \left( \texttt{libra_challenge} \cdot \texttt{libra_total_sum} - \left(\texttt{libra_univariates}_{0}(0) + \texttt{libra_univariates}_{0}(1)\right) \right). \f} + +These entities are created inside \ref bb::SumcheckProver< Flavor >::setup_zk_sumcheck_data "setup_zk_sumcheck_data" and stored in a \ref bb::SumcheckProverRound< Flavor >::ZKSumcheckData "ZKSumcheckData structure" zk_sumcheck_data. +All the modifications between the Sumcheck rounds are performed by the method \ref bb::SumcheckProver< Flavor >::update_libra_data "update Libra data" that is called by \ref bb::SumcheckProver< Flavor >::update_zk_sumcheck_data "update_zk_sumcheck_data". + ### First Round {#LibraFirstRound} -The prover computes first Libra round univariate +The prover computes the first Libra round univariate \f{align}{ -\texttt{libra*univariate}\_0(X_0) = \rho \cdot \sum*{\vec \ell \in \{0,1\}^{d-1}} G(X*0,\vec \ell) = -2^{d-1} \rho\cdot g_0(X_0) + 2^{d-1} \rho \cdot \sum*{i=1}^{d-1}\left(g*i(0)+g_i(1)\right) + \texttt{libra_round_univariate}_0(X_0) = \texttt{libra_challenge} \cdot \sum_{\vec \ell \in \{0,1\}^{d-1}} G(X_0,\vec \ell) = + 2^{d-1} \texttt{libra_challenge}\cdot g_0(X_0) + 2^{d-2} \texttt{libra_challenge} \cdot \sum_{i=1}^{d-1}\left(g_i(0)+g_i(1)\right). \f} -which could be expressed as follows +By design of the method \ref bb::SumcheckProver< Flavor >::setup_zk_sumcheck_data "setup_zk_sumcheck_data", the latter could be expressed as follows \f{align}{ -\texttt{libra_univariate}\_0 (k) \gets \texttt{libra_table}*{0,k} + \texttt{libra_running_sum} + \texttt{libra_round_univariate}_0 (k) \gets \texttt{libra_univariates}_{0}(k) + \texttt{libra_running_sum} \f} -for \f$k=0,\ldots, \tilde{D}\f$. +for \f$k=0,\ldots, \tilde{D}\f$. It is done by the method \ref bb::SumcheckProverRound< Flavor >::compute_libra_round_univariate "compute_libra_round_univariate" called inside \ref bb::SumcheckProverRound< Flavor >::compute_univariate "Sumcheck Round Univariate computation", which also takes care of adding \f$\texttt{libra_round_univariate}\f$ to the \f$\texttt{round_unviariate}\f$. -When the prover receives the challenge \f$u_0\f$, it computes the value \f$g_0(u_0)\f$ using \ref bb::Univariate::evaluate "evaluate" method, updates the running sum +When the prover receives the challenge \f$u_0\f$, it \ref bb::SumcheckProver< Flavor >::update_libra_data "updates Libra data": + +- updates the table of Libra univariates by multiplying every term by \f$1/2\f$. +- computes the value \f$2^{d-2} \cdot \texttt{libra_challenge} \cdot g_0(u_0)\f$ applying \ref bb::Univariate::evaluate "evaluate" method to the first univariate in the table \f$\texttt{libra_univariates}\f$ +- places the value \f$ g_0(u_0)\f$ to the vector \f$ \texttt{libra_evaluations}\f$ +- updates the running sum \f{align}{ -\texttt{libra*running_sum} \gets 2^{-1} \cdot \left( (g_0(u_0) + \texttt{libra_running_sum}) - (\texttt{libra_table}*{1,0} + \texttt{libra*table}*{1,1})\right) + \texttt{libra_running_sum} \gets 2^{d-2} \cdot \texttt{libra_challenge} \cdot g_0(u_0) + 2^{-1} \cdot \left( \texttt{libra_running_sum} - (\texttt{libra_univariates}_{1}(0) + \texttt{libra_univariates}_{1}(1)) \right) \f} -and updates the libra table by releasing the first column and multiplying reamining terms by \f$1/2\f$. ### Round Univariates in Subsequent Rounds {#LibraRoundUnivariates} - -Similarly, to compute the contribution of Libra masking polynomial \f$G\f$ to the round univariates \f$\tilde{S}_i\f$ defined in [Compute Round Univariates](#ComputeRoundUnivariates), consider +In Round \f$ i \f$, the prover computes \f$ i \f$-th Libra round univariate \f{align}{ -\texttt{libra*univariate}\_i(X_i) = \rho \cdot \sum*{\vec \ell \in \{0,1\}^{d-1 - i}} G(u*0,\ldots, u*{i-1}, X*{i}, \vec \ell) = -\rho \cdot 2^{d-1 - i} \left( \sum*{j = 0}^{i-1} g*j(u*{j}) + g*{i}(X_i) + \sum*{j=i+1}^{d-1} \left(g*{j,0} + g*{j,1}\right) \right) +\texttt{libra_univariate}_i(X_i) = \texttt{libra_challenge} \cdot \sum_{\vec \ell \in \{0,1\}^{d-1 - i}} G(u_0,\ldots, u_{i-1}, X_{i}, \vec \ell) = +\texttt{libra_challenge} \cdot 2^{d-1 - i} \left( \sum_{j = 0}^{i-1} g_j(u_{j}) + g_{i}(X_i) + \sum_{j=i+1}^{d-1} \left(g_{j,0} + g_{j,1}\right) \right) \f} -Therefore, the contribution of the \f$\texttt{libra_univariate}_{i}(X_{i})\f$ at \f$X_{i} = k\f$ to \f$\tilde{S}^i(k)\f$, where \f$k=0,\ldots, \tilde{D}\f$, is given by the formula + +By design of the method \ref bb::SumcheckProver< Flavor >::update_zk_sumcheck_data "update_zk_sumcheck_data", the latter could be expressed as follows \f{align}{ -\texttt{libra*univariate}\_i(k) = \rho \cdot 2^{d-1-i} \left(\sum*{j = 0}^{i-1} g*j(u*{j}) + g*{i,k}+ \sum*{j=i+1}^{d-1}\left(g*{j,0}+g*{j,1}\right)\right) = \texttt{libra*table}*{i,k} + \texttt{libra_running_sum}. + \texttt{libra_round_univariate}_i (k) \gets \texttt{libra_univariates}_{i}(k) + \texttt{libra_running_sum} \f} +for \f$k=0,\ldots, \tilde{D}\f$. This computation is done by the method \ref bb::SumcheckProverRound< Flavor >::compute_libra_round_univariate "compute_libra_round_univariate" called inside \ref bb::SumcheckProverRound< Flavor >::compute_univariate "Sumcheck Round Univariate computation", which also adds \f$\texttt{libra_round_univariate}\f$ to the \f$\texttt{round_unviariate}\f$. -### Updating Partial Evaluations {#LibraUpdatePartialEvaluations} -In Rounds \f$ i = 1,\ldots d-2\f$, after correcting Sumcheck round univariate \f$S*{i}(X*{i})\f$ by \f$ \texttt{libra*univariate}\_i(X_i)\f$, the prover gets the challenge \f$u*{i}\f$, computes the value \f$\texttt{libra*univariate}*{i}(u*{i})\f$ and updates the running sum -\f{align}{ -\texttt{libra_running_sum} \gets 2^{-1} \cdot \left( (g_i(u_i) + \texttt{libra_running_sum}) - (\texttt{libra_table}*{i+1,0} + \texttt{libra*table}*{i+1,1})\right) -\f} +### Updating Libra Data in Subsequent Rounds {#UpdateLibraData} -### Final Round {#LibraFinalRound} +When the prover receives new challenge \f$u_i\f$, it \ref bb::SumcheckProver< Flavor >::update_libra_data "updates Libra data". If \f$ i < d-1\f$, the prover -After sending the evaluations of \f$\texttt{libra_univariate}_{d-1}\f$ at over the domain \f$\{0,\ldots, \tilde{D}\}\f$, the prover gets the last challenge \f$u_{d-1}\f$ and has to send the claimed evaluation \f$G(u_0,\ldots, u_{d-1})\f$. It boils down to sending and proving the evaluations +- updates the table of Libra univariates by multiplying every term by \f$1/2\f$. +- computes the value \f$2^{d-i - 2} \cdot \texttt{libra_challenge} \cdot g_0(u_0)\f$ applying \ref bb::Univariate::evaluate "evaluate" method to the first univariate in the table \f$\texttt{libra_univariates}\f$ +- places the value \f$ g_0(u_0)\f$ to the vector \f$ \texttt{libra_evaluations}\f$ +- updates the running sum \f{align}{ -v_i = g_i(u_i) \text{ for } i = 0,\ldots, d-1. + \texttt{libra_running_sum} \gets 2^{d-i-2} \cdot \texttt{libra_challenge} \cdot g_0(u_0) + 2^{-1} \cdot \left( \texttt{libra_running_sum} - (\texttt{libra_univariates}_{i+1}(0) + \texttt{libra_univariates}_{i+1}(1)) \right) \f} +If \f$ i = d-1\f$, the prover +- computes the value \f$ g_{d-1}(u_{d-1})\f$ applying \ref bb::Univariate::evaluate "evaluate" method to the last univariate in the table \f$\texttt{libra_univariates}\f$ and dividing the result by \f$ \texttt{libra_challenge} \f$. +- updates the table of Libra univariates by multiplying every term by \f$\texttt{libra_challenge}^{-1}\f$. + +### Proving the Evaluations of Libra Univariates {#ProvingLibraEvaluations} +Libra claimed evaluations \f$ g_0(u_0), \ldots, g_{d-1}(u_d-1)\f$ have to proved using shplonk . + ## Libra Costs {#LibraCosts} @@ -318,18 +333,18 @@ Using the PCS introduced in Section 4 of ::create_evaluation_masking_table "creates the vector" of univariates \f{align}{ -\texttt{masking*terms_evaluations}*{k,j}\gets \rho*j \cdot (1-k) k +\texttt{masking_terms_evaluations}_j(k)\gets \texttt{eval_masking_scalars}_j \cdot (1-k) k \f} -for \f$j=1, \ldots, N_w\f$ and \f$ k=2,\ldots, \tilde{D} \f$ and stores the vector of running quadratic terms +of the same size as the ExtendedEdges created by the ZK Flavor running Sumcheck. + +When the prover receives the challenge \f$ u_i \f$, this vector is \ref bb::SumcheckProver< Flavor >::update_masking_terms_evaluations "updated" as follows + \f{align}{ -\texttt{running_quadratic_term}\_j \gets \rho_j \cdot \sum*{k=0}^{i-1} (1-u_k) u_k. + \texttt{masking_terms_evaluations}_j(k) \gets \texttt{eval_masking_scalars}_j \cdot u_i \cdot (1-u_i) \f} ### Computing Evaluations of Round Univariates {#RoundUnivariatesMaskedEval} In Round \f$i \in \{0,\ldots, d-1\}\f$, the prover computes univariate polynomials \f{align}{ -\widehat{S}^i(X*i) = \sum*{\vec\ell \in \{0,1\}^{d-1-i}} F\left(\widehat{P}_1(u_0,\ldots, u_{i-1}, X*i, \vec \ell),\ldots,\widehat{P}*{N*w}(u_0,\ldots, u*{i-1}, X*i, \vec \ell), P*{N*w+1}(u_0,\ldots, u*{i-1}, X*i, \vec \ell), \ldots, P*{N}(u*0,\ldots, u*{i-1}, X*i, \vec \ell) \right) +\widehat{S}^i(X_i) = \sum_{\vec\ell \in \{0,1\}^{d-1-i}} F\left(\widehat{P}_1(u_0,\ldots, u_{i-1}, X_i, \vec \ell),\ldots,\widehat{P}_{N_w}(u_0,\ldots, u_{i-1}, X_i, \vec \ell), P_{N_w+1}(u_0,\ldots, u_{i-1}, X_i, \vec \ell), \ldots, P_{N}(u_0,\ldots, u_{i-1}, X_i, \vec \ell) \right) \f} which reduces to computing at most \f$ (D+ D_w + 1) \times N \times 2^{d-1 - i}\f$ values \f{align}{ -&\ P_j(u_0,\ldots, u*{i-1}, k, \vec \ell) + \rho*j \cdot \sum*{k=0}^{i-1} u*k(1-u_k) + \rho_j\cdot (1-k) k \quad \text{ for } j=1,\ldots, N_w\\ -&\ P_j(u_0,\ldots, u*{i-1}, k, \vec \ell) \quad \text { for } j= N*w+1,\ldots, N +&\ P_j(u_0,\ldots, u_{i-1}, k, \vec \ell) + \rho_j \cdot \sum_{k=0}^{i-1} u_k(1-u_k) + \rho_j\cdot (1-k) k \quad \text{ for } j=1,\ldots, N_w\\ +&\ P_j(u_0,\ldots, u_{i-1}, k, \vec \ell) \quad \text { for } j= N_w+1,\ldots, N +\f} +By design, we have +\f{align}{ + \texttt{masking_terms_evaluations}_j(k) = \rho_j \cdot \sum_{k=0}^{i-1} u_k(1-u_k) + \rho_j\cdot (1-k) k. \f} -The values \f$ \texttt{running_quadratic_term}\_j = \rho_j \cdot \sum*{k=0}^{i-1} u_k(1-u_k)\f$ are available from Round \f$i-1\f$. -The products \f$ \rho_j \cdot (1-k) k\f$ are taken from the table \f$ \texttt{masking_terms_evaluations}\f$. -The prover performs an extra addition per evaluation \f$\widehat{P}_j(u_0,\ldots, u_{i-1}, k, \vec \ell)\f$ for \f$k=0,1\f$ and two extra additions per evaluation for \f$k=2,\ldots, D+D_w\f$ compared to evaluating the original witness polynomials \f$P_j\f$. -It results in \f$2 (D+D_w) N_w (2^d-1) \f$ extra additions compared to [Non-ZK-Sumcheck](#NonZKSumcheck). +Then the method \ref bb::SumcheckProverRound< Flavor >::extend_zk_edges "extend_zk_edges" gets the \f$j\f$-th edge corresponding to the witness polynomial and corrects it with the univariate \f$ \texttt{masking_terms_evaluations}_j\f$. The non-witness polynomials are treated as in \ref bb::SumcheckProverRound< Flavor >::extend_edges "extend_edges" used in non-ZK Flavors. -Upon receiving the round challenge \f$ u_i\f$, the prover prepares the correcting term for the next round -\f{align}{ -\texttt{running_quadratic_terms}\_j \gets \texttt{running_quadratic_terms}\_j + \rho_j \cdot (1-u_i) u_i . -\f} ### Witness Evaluation Masking Costs {#MaskingCosts} +The prover performs an extra addition per evaluation \f$\widehat{P}_j(u_0,\ldots, u_{i-1}, k, \vec \ell)\f$ for \f$k=0,1\f$ and two extra additions per evaluation for \f$k=2,\ldots, D+D_w\f$ compared to evaluating the original witness polynomials \f$P_j\f$. +It results in \f$2 (D+D_w) N_w (2^d-1) \f$ extra additions compared to [Non-ZK-Sumcheck](#NonZKSumcheck). In contrast to non-ZK-Sumcheck, the prover needs to compute \f$\tilde{D} \sim D+D_w \f$ evaluations of round univariates \f$S_i\f$, which results in \f{align}{ @@ -465,9 +486,9 @@ The total costs of ZK Sumcheck are obtained from [Libra Costs](#LibraCosts) and ## Theoretic Field Operations vs. Implementation The table above sets a reasonable upper bound on the amount of prover's field operations. -However, in the implementation, the relation \f$ F \f$ is computed as a vector of its subrelations, which allows us to decrease the costs of computing the round univariates. Namely, for a given subrelation \f$ F*j \f$, its maximum partial degree \f$D_j\f$ and its witness degree \f$D*{w,j} \f$ are generally less than \f$ D\f$ and \f$ D*w \f$, respectively. +However, in the implementation, the relation \f$ F \f$ is computed as a vector of its subrelations, which allows us to decrease the costs of computing the round univariates. Namely, for a given subrelation \f$ F_j \f$, its maximum partial degree \f$D_j\f$ and its witness degree \f$D_{w,j} \f$ are generally less than \f$ D\f$ and \f$ D_w \f$, respectively. Therefore, we compute \f$ F_j \f$'s contribution to Sumcheck Round Univariates by evaluating the univariate polynomial \f{align}{ -\sum*{\vec \ell\in \{0,1\}^{d-1-i}} pow*{\beta}(u_0,\ldots, u*{i-1}, X*i, \vec \ell) \cdot F_j(u_0,\ldots, u*{i-1}, X*i,\vec \ell) +\sum_{\vec \ell\in \{0,1\}^{d-1-i}} pow_{\beta}(u_0,\ldots, u_{i-1}, X_i, \vec \ell) \cdot F_j(u_0,\ldots, u_{i-1}, X_i,\vec \ell) \f} -at \f$ X_i = 0,\ldots, D_i + D*{w,i}\f$ and extend the resulting univariate of degree \f$D_j+D_{w,j}\f$ to the entire domain \f$\{ 0,\ldots, D+D_w\}\f$, which is way cheaper than evaluating the sum above at \f$ X*i = D*{j}+ D\_{w,j}+1, \ldots, D+ D_w \f$ +at \f$ X_i = 0,\ldots, D_i + D_{w,i}\f$ and extend the resulting univariate of degree \f$D_j+D_{w,j}\f$ to the entire domain \f$\{ 0,\ldots, D+D_w\}\f$, which is way cheaper than evaluating the sum above at \f$ X_i = D_{j}+ D\_{w,j}+1, \ldots, D+ D_w \f$ diff --git a/barretenberg/cpp/src/barretenberg/flavor/flavor.hpp b/barretenberg/cpp/src/barretenberg/flavor/flavor.hpp index db63d9cb0cd..0c2845a3021 100644 --- a/barretenberg/cpp/src/barretenberg/flavor/flavor.hpp +++ b/barretenberg/cpp/src/barretenberg/flavor/flavor.hpp @@ -443,6 +443,8 @@ template concept IsFoldingFlavor = IsAnyOf, MegaRecursiveFlavor_, MegaRecursiveFlavor_, MegaRecursiveFlavor_>; +template +concept FlavorHasZK = T::HasZK; template inline std::string flavor_get_label(Container&& container, const Element& element) { diff --git a/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/ultra_keccak.hpp b/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/ultra_keccak.hpp index 7f28be204a2..ecb74ee5494 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/ultra_keccak.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/ultra_keccak.hpp @@ -33,6 +33,8 @@ class UltraKeccakFlavor { using CommitmentKey = bb::CommitmentKey; using VerifierCommitmentKey = bb::VerifierCommitmentKey; + // Indicates that this flavor runs with non-ZK Sumcheck. + static constexpr bool HasZK = false; 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`. @@ -42,6 +44,8 @@ class UltraKeccakFlavor { static constexpr size_t NUM_PRECOMPUTED_ENTITIES = 25; // The total number of witness entities not including shifts. static constexpr size_t NUM_WITNESS_ENTITIES = 8; + // The total number of witnesses including shifts and derived entities. + static constexpr size_t NUM_ALL_WITNESS_ENTITIES = 13; // Total number of folded polynomials, which is just all polynomials except the shifts static constexpr size_t NUM_FOLDED_ENTITIES = NUM_PRECOMPUTED_ENTITIES + NUM_WITNESS_ENTITIES; diff --git a/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck.hpp b/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck.hpp index d5f71eec3c1..a25bf3f8d81 100644 --- a/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck.hpp +++ b/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck.hpp @@ -1,5 +1,6 @@ #pragma once #include "barretenberg/plonk_honk_shared/library/grand_product_delta.hpp" +#include "barretenberg/polynomials/polynomial_arithmetic.hpp" #include "barretenberg/sumcheck/instance/prover_instance.hpp" #include "barretenberg/sumcheck/sumcheck_output.hpp" #include "barretenberg/transcript/transcript.hpp" @@ -120,23 +121,44 @@ template class SumcheckProver { using ProverPolynomials = typename Flavor::ProverPolynomials; using PartiallyEvaluatedMultivariates = typename Flavor::PartiallyEvaluatedMultivariates; using ClaimedEvaluations = typename Flavor::AllValues; + using Transcript = typename Flavor::Transcript; using Instance = ProverInstance_; using RelationSeparator = typename Flavor::RelationSeparator; + /** + * @brief The total algebraic degree of the Sumcheck relation \f$ F \f$ as a polynomial in Prover Polynomials + * \f$P_1,\ldots, P_N\f$. + */ + static constexpr size_t MAX_PARTIAL_RELATION_LENGTH = Flavor::MAX_PARTIAL_RELATION_LENGTH; + // this constant specifies the number of coefficients of libra polynomials, and evaluations of round univariate + static constexpr size_t BATCHED_RELATION_PARTIAL_LENGTH = Flavor::BATCHED_RELATION_PARTIAL_LENGTH; + // Specify the number of all witnesses including shifts and derived witnesses from flavors that have ZK, + // otherwise, set this constant to 0 + static constexpr size_t NUM_ALL_WITNESS_ENTITIES = Flavor::NUM_ALL_WITNESS_ENTITIES; /** * @brief The size of the hypercube, i.e. \f$ 2^d\f$. * */ + + using SumcheckRoundUnivariate = typename bb::Univariate; + using EvaluationMaskingTable = + std::array, NUM_ALL_WITNESS_ENTITIES>; const size_t multivariate_n; /** * @brief The number of variables * */ const size_t multivariate_d; + using EvalMaskingScalars = std::array; + // Define the length of Libra Univariates. For non-ZK Flavors: set to 0. + static constexpr size_t LIBRA_UNIVARIATES_LENGTH = Flavor::HasZK ? Flavor::BATCHED_RELATION_PARTIAL_LENGTH : 0; + using LibraUnivariates = std::vector>; std::shared_ptr transcript; SumcheckProverRound round; + // Declare a container for ZK Sumcheck data + ZKSumcheckData zk_sumcheck_data; /** * @@ -178,57 +200,94 @@ template class SumcheckProver { * @param gate_challenges * @return SumcheckOutput */ - SumcheckOutput prove(ProverPolynomials& full_polynomials, const bb::RelationParameters& relation_parameters, const RelationSeparator alpha, const std::vector& gate_challenges) { - + // In case the Flavor has ZK, we populate sumcheck data structure with randomness, compute correcting term for + // the total sum, etc. + if constexpr (Flavor::HasZK) { + setup_zk_sumcheck_data(zk_sumcheck_data); + }; bb::PowPolynomial pow_univariate(gate_challenges); pow_univariate.compute_values(); - std::vector multivariate_challenge; multivariate_challenge.reserve(multivariate_d); - + size_t round_idx = 0; // In the first round, we compute the first univariate polynomial and populate the book-keeping table of - // #partially_evaluated_polynomials, which has \f$ n/2 \f$ rows and \f$ N \f$ columns. - auto round_univariate = round.compute_univariate(full_polynomials, relation_parameters, pow_univariate, alpha); + // #partially_evaluated_polynomials, which has \f$ n/2 \f$ rows and \f$ N \f$ columns. When the Flavor has ZK, + // compute_univariate also takes into account the zk_sumcheck_data. + auto round_univariate = round.compute_univariate( + round_idx, full_polynomials, relation_parameters, pow_univariate, alpha, zk_sumcheck_data); + // Place the evaluations of the round univariate into transcript. transcript->send_to_verifier("Sumcheck:univariate_0", round_univariate); FF round_challenge = transcript->template get_challenge("Sumcheck:u_0"); multivariate_challenge.emplace_back(round_challenge); + // Prepare sumcheck book-keeping table for the next round partially_evaluate(full_polynomials, multivariate_n, round_challenge); + // Prepare ZK Sumcheck data for the next round + if constexpr (Flavor::HasZK) { + update_zk_sumcheck_data(zk_sumcheck_data, round_challenge, round_idx); + }; pow_univariate.partially_evaluate(round_challenge); round.round_size = round.round_size >> 1; // TODO(#224)(Cody): Maybe partially_evaluate should do this and // release memory? // All but final round // We operate on partially_evaluated_polynomials in place. for (size_t round_idx = 1; round_idx < multivariate_d; round_idx++) { // Write the round univariate to the transcript - round_univariate = - round.compute_univariate(partially_evaluated_polynomials, relation_parameters, pow_univariate, alpha); + round_univariate = round.compute_univariate(round_idx, + partially_evaluated_polynomials, + relation_parameters, + pow_univariate, + alpha, + zk_sumcheck_data); + // Place evaluations of Sumcheck Round Univariate in the transcript transcript->send_to_verifier("Sumcheck:univariate_" + std::to_string(round_idx), round_univariate); FF round_challenge = transcript->template get_challenge("Sumcheck:u_" + std::to_string(round_idx)); multivariate_challenge.emplace_back(round_challenge); + // Prepare sumcheck book-keeping table for the next round partially_evaluate(partially_evaluated_polynomials, round.round_size, round_challenge); + // Prepare evaluation masking and libra structures for the next round (for ZK Flavors) + if constexpr (Flavor::HasZK) { + update_zk_sumcheck_data(zk_sumcheck_data, round_challenge, round_idx); + }; + pow_univariate.partially_evaluate(round_challenge); round.round_size = round.round_size >> 1; } + // Check that the challenges \f$ u_0,\ldots, u_{d-1} \f$ do not satisfy the equation \f$ u_0(1-u_0) + \ldots + + // u_{d-1} (1 - u_{d-1}) = 0 \f$. This equation is satisfied with probability ~ 1/|FF|, in such cases the prover + // has to abort and start ZK Sumcheck anew. + if constexpr (Flavor::HasZK) { + check_that_evals_do_not_leak_witness_data(multivariate_challenge); + }; + // Zero univariates are used to pad the proof to the fixed size CONST_PROOF_SIZE_LOG_N. auto zero_univariate = bb::Univariate::zero(); for (size_t idx = multivariate_d; idx < CONST_PROOF_SIZE_LOG_N; idx++) { transcript->send_to_verifier("Sumcheck:univariate_" + std::to_string(idx), zero_univariate); FF round_challenge = transcript->template get_challenge("Sumcheck:u_" + std::to_string(idx)); multivariate_challenge.emplace_back(round_challenge); } - - // Final round: Extract multivariate evaluations from #partially_evaluated_polynomials and add to transcript + // The evaluations of Libra uninvariates at \f$ g_0(u_0), \ldots, g_{d-1} (u_{d-1}) \f$ are added to the + // transcript. + if constexpr (Flavor::HasZK) { + transcript->send_to_verifier("Libra:evaluations", zk_sumcheck_data.libra_evaluations); + }; + + // Claimed evaluations of Prover polynomials are extracted and added to the transcript. When Flavor has ZK, the + // evaluations of all witnesses are masked. ClaimedEvaluations multivariate_evaluations; - for (auto [eval, poly] : - zip_view(multivariate_evaluations.get_all(), partially_evaluated_polynomials.get_all())) { - eval = poly[0]; - } + multivariate_evaluations = extract_claimed_evaluations(partially_evaluated_polynomials); transcript->send_to_verifier("Sumcheck:evaluations", multivariate_evaluations.get_all()); - - return { multivariate_challenge, multivariate_evaluations }; + // For ZK Flavors: the evaluations of Libra univariates are included in the Sumcheck Output + if constexpr (!Flavor::HasZK) { + return SumcheckOutput{ multivariate_challenge, multivariate_evaluations }; + } else { + return SumcheckOutput{ multivariate_challenge, + multivariate_evaluations, + zk_sumcheck_data.libra_evaluations }; + } }; /** @@ -291,6 +350,278 @@ template class SumcheckProver { } }); }; + + /** + * @brief This method takes the book-keeping table containing partially evaluated prover polynomials and creates a + * vector containing the evaluations of all prover polynomials at the point \f$ (u_0, \ldots, u_{d-1} )\f$. + * For ZK Flavors: this method takes the book-keeping table containing partially evaluated prover polynomials +and creates a vector containing the evaluations of all witness polynomials at the point \f$ (u_0, \ldots, u_{d-1} )\f$ +masked by the terms \f$ \texttt{eval_masking_scalars}_j\cdot \sum u_i(1-u_i)\f$ and the evaluations of all non-witness +polynomials that are sent in clear. + * + * @param partially_evaluated_polynomials + * @param multivariate_evaluations + */ + ClaimedEvaluations extract_claimed_evaluations(PartiallyEvaluatedMultivariates& partially_evaluated_polynomials) + { + ClaimedEvaluations multivariate_evaluations; + if constexpr (!Flavor::HasZK) { + for (auto [eval, poly] : + zip_view(multivariate_evaluations.get_all(), partially_evaluated_polynomials.get_all())) { + eval = poly[0]; + }; + } else { + // Extract claimed evaluations of non-witness polynomials + for (auto [eval, poly] : zip_view(multivariate_evaluations.get_non_witnesses(), + partially_evaluated_polynomials.get_non_witnesses())) { + eval = poly[0]; + }; + // Extract claimed evaluations of all witness polynomials + for (auto [eval, poly, masking_term] : zip_view(multivariate_evaluations.get_all_witnesses(), + partially_evaluated_polynomials.get_all_witnesses(), + zk_sumcheck_data.masking_terms_evaluations)) { + eval = poly[0] + masking_term.value_at(0); + } + } + return multivariate_evaluations; + }; + + /** + * @brief Create and populate the structure required for the ZK Sumcheck. + + * @details This method creates an array of random field elements \f$ \rho_1,\ldots, \rho_{N_w}\f$ aimed to mask the + evaluations of witness polynomials, these are contained in \f$ \texttt{eval_masking_scalars} \f$. In order to + optimize the computation of Sumcheck Round Univariates, it populates a table of univariates \f$ + \texttt{masking_terms_evaluations} \f$ which contains at the beginning the evaluations of polynomials \f$ \rho_j + \cdot (1-X)\cdot X \f$ at \f$ 0,\ldots, \text{MAX_PARTIAL_RELATION_LENGTH} - 1\f$. This method also creates Libra + univariates, computes the Libra total sum and adds it to the transcript, and sets up all auxiliary objects. + * + * @param zk_sumcheck_data + */ + void setup_zk_sumcheck_data(ZKSumcheckData& zk_sumcheck_data) + { + + EvalMaskingScalars eval_masking_scalars; + + for (size_t k = 0; k < NUM_ALL_WITNESS_ENTITIES; ++k) { + eval_masking_scalars[k] = FF::random_element(); + }; + // Generate random scalars \f$ \rho_1,\ldots, \rho_{N_w}\f$ to mask the evaluations of witness polynomials and + // populate the table masking_terms_evaluations with the terms \f$ \rho_j \cdot (1-k) \cdot k \f$ + auto masking_terms_evaluations = create_evaluation_masking_table(eval_masking_scalars); + // Generate random Libra Polynomials to mask Round Univariates. + LibraUnivariates libra_univariates = generate_libra_polynomials(multivariate_d); + // have to commit to libra_univariates here + auto libra_scaling_factor = FF(1); + FF libra_total_sum = compute_libra_total_sum(libra_univariates, libra_scaling_factor); + transcript->send_to_verifier("Libra:Sum", libra_total_sum); + // get the challenge for the zk-sumcheck claim \sigma + \rho \cdot libra_total_sum + FF libra_challenge = transcript->template get_challenge("Libra:Challenge"); + // Initialize Libra running sum by multiplpying it by Libra challenge \f$\rho\f$; + auto libra_running_sum = libra_total_sum * libra_challenge; + // Multiply the column-univariates of the array of libra polynomials by libra challenge and power of \f$ 2\f$, + // modify libra running_sum subtracting the contribution from the first univariate + setup_libra_data(libra_univariates, libra_scaling_factor, libra_challenge, libra_running_sum); + + std::vector libra_evaluations; + libra_evaluations.reserve(multivariate_d); + zk_sumcheck_data = ZKSumcheckData(eval_masking_scalars, + masking_terms_evaluations, + libra_univariates, + libra_scaling_factor, + libra_challenge, + libra_running_sum, + libra_evaluations); + }; + + /** + * @brief Given number of univariate polynomials and the number of their evaluations meant to be hidden, this method + * produces a vector of univariate polynomials of degree \ref ZK_BATCHED_LENGTH "ZK_BATCHED_LENGTH - 1" with + * independent uniformly random coefficients. + * + */ + static LibraUnivariates generate_libra_polynomials(size_t number_of_polynomials) + { + LibraUnivariates libra_full_polynomials(number_of_polynomials); + for (auto& libra_polynomial : libra_full_polynomials) { + // generate random polynomial of required size + libra_polynomial = bb::Univariate::get_random(); + }; + + return libra_full_polynomials; + }; + /** + * @brief Generate an array of random scalars of size equal to the number of all witness polynomials and populate a + * table of evaluations of the quadratic terms needed for masking evaluations of witnesses. + * + * @param evaluations + */ + static EvaluationMaskingTable create_evaluation_masking_table(EvalMaskingScalars eval_masking_scalars) + { + EvaluationMaskingTable output_table; + for (size_t column_idx = 0; column_idx < NUM_ALL_WITNESS_ENTITIES; ++column_idx) { + for (size_t row_idx = 0; row_idx < MAX_PARTIAL_RELATION_LENGTH; ++row_idx) { + auto scalar = FF(row_idx); + output_table[column_idx].value_at(row_idx) = + scalar * (FF(1) - scalar) * eval_masking_scalars[column_idx]; + }; + }; + return output_table; + }; + + /** + * @brief Update the table of masking quadratic terms by adding a contribution from a current challenge. + * + @details At initialization, \f$j\f$'th column of the masking terms evaluations table is a vector \f$(0, 0, \rho_2 + \cdot 2, \ldots, \rho_j \cdot k (1-k), \ldots, \rho_j \cdot (D-1) (1-(D-1)))\f$. Upon getting current round + challenge, the prover adds the term \f$ \rho_j \cdot u_i \cdot (1-u_i)\f$ to each entry in the table. + + It is useful at the stage of evaluating the relation \f$ \tilde{F} \f$ at the arguments given by the values of + \f$(\widehat{P}_1, \ldots, \widehat{P}_{N_w})\f$ at the points \f$u_0,\ldots, u_{i}, k, \vec \ell)\f$. + * @param evaluations + * @param masking_scalars + * @param round_challenge + */ + void update_masking_terms_evaluations(ZKSumcheckData& zk_sumcheck_data, FF round_challenge) + { + for (auto [masking_term, masking_scalar] : + zip_view(zk_sumcheck_data.masking_terms_evaluations, zk_sumcheck_data.eval_masking_scalars)) { + for (size_t k = 0; k < MAX_PARTIAL_RELATION_LENGTH; ++k) { + masking_term.value_at(k) += round_challenge * (FF(1) - round_challenge) * masking_scalar; + } + } + } + /** + * @brief Compute the sum of the randomly sampled multivariate polynomial \f$ G = \sum_{i=0}^{n-1} g_i(X_i) \f$ over + * the Boolean hypercube. + * + * @param libra_univariates + * @param scaling_factor + * @return FF + */ + static FF compute_libra_total_sum(auto libra_univariates, FF& scaling_factor) + { + FF total_sum = 0; + scaling_factor = scaling_factor / 2; + + for (auto univariate : libra_univariates) { + total_sum += univariate.value_at(0) + univariate.value_at(1); + scaling_factor *= 2; + } + total_sum *= scaling_factor; + + return total_sum; + } + /** + * @brief Set up Libra book-keeping table that simplifies the computation of Libra Round Univariates + * + * @details The array of Libra univariates is getting scaled + * \f{align}{ + \texttt{libra_univariates} \gets \texttt{libra_univariates}\cdot \rho \cdot 2^{d-1} + \f} + * We also initialize + * \f{align}{ + \texttt{libra_running_sum} \gets \texttt{libra_total_sum} - \texttt{libra_univariates}_{0,0} - + \texttt{libra_univariates}_{0,1} \f}. + * @param libra_table + * @param libra_round_factor + * @param libra_challenge + */ + void setup_libra_data(auto& libra_univariates, + FF& libra_scaling_factor, + const FF libra_challenge, + FF& libra_running_sum) + { + libra_scaling_factor *= libra_challenge; // \rho * 2^{d-1} + for (auto& univariate : libra_univariates) { + univariate *= libra_scaling_factor; + }; + // subtract the contribution of the first libra univariate from libra total sum + libra_running_sum += -libra_univariates[0].value_at(0) - libra_univariates[0].value_at(1); + libra_running_sum *= FF(1) / FF(2); + } + + /** + * @brief Upon receiving the challenge \f$u_i\f$, the prover updates Libra data. If \f$ i < d-1\f$ + + - update the table of Libra univariates by multiplying every term by \f$1/2\f$. + - computes the value \f$2^{d-i - 2} \cdot \texttt{libra_challenge} \cdot g_0(u_0)\f$ applying \ref + bb::Univariate::evaluate "evaluate" method to the first univariate in the table \f$\texttt{libra_univariates}\f$ + - places the value \f$ g_0(u_0)\f$ to the vector \f$ \texttt{libra_evaluations}\f$ + - update the running sum + \f{align}{ + \texttt{libra_running_sum} \gets 2^{d-i-2} \cdot \texttt{libra_challenge} \cdot g_0(u_0) + 2^{-1} + \cdot \left( \texttt{libra_running_sum} - (\texttt{libra_univariates}_{i+1}(0) + + \texttt{libra_univariates}_{i+1}(1)) \right) \f} If \f$ i = d-1\f$ + - compute the value \f$ g_{d-1}(u_{d-1})\f$ applying \ref bb::Univariate::evaluate "evaluate" method to the + last univariate in the table \f$\texttt{libra_univariates}\f$ and dividing the result by \f$ + \texttt{libra_challenge} \f$. + - update the table of Libra univariates by multiplying every term by \f$\texttt{libra_challenge}^{-1}\f$. + @todo Refactor once the Libra univariates are extracted from the Proving Key. Then the prover does not need to + update the first round_idx - 1 univariates and could release the memory. Also, use batch_invert / reduce + the number of divisions by 2. + * @param libra_univariates + * @param round_challenge + * @param round_idx + * @param libra_running_sum + * @param libra_evaluations + */ + void update_libra_data(ZKSumcheckData& zk_sumcheck_data, const FF round_challenge, size_t round_idx) + { + // when round_idx = d - 1, the update is not needed + if (round_idx < zk_sumcheck_data.libra_univariates.size() - 1) { + for (auto& univariate : zk_sumcheck_data.libra_univariates) { + univariate *= FF(1) / FF(2); + }; + // compute the evaluation \f$ \rho \cdot 2^{d-2-i} \çdot g_i(u_i) \f$ + auto libra_evaluation = zk_sumcheck_data.libra_univariates[round_idx].evaluate(round_challenge); + auto next_libra_univariate = zk_sumcheck_data.libra_univariates[round_idx + 1]; + // update the running sum by adding g_i(u_i) and subtracting (g_i(0) + g_i(1)) + zk_sumcheck_data.libra_running_sum += + -next_libra_univariate.value_at(0) - next_libra_univariate.value_at(1); + zk_sumcheck_data.libra_running_sum *= FF(1) / FF(2); + + zk_sumcheck_data.libra_running_sum += libra_evaluation; + zk_sumcheck_data.libra_scaling_factor *= FF(1) / FF(2); + + zk_sumcheck_data.libra_evaluations.emplace_back(libra_evaluation / zk_sumcheck_data.libra_scaling_factor); + } else { + // compute the evaluation of the last Libra univariate at the challenge u_{d-1} + auto libra_evaluation = zk_sumcheck_data.libra_univariates[round_idx].evaluate(round_challenge) / + zk_sumcheck_data.libra_scaling_factor; + // place the evalution into the vector of Libra evaluations + zk_sumcheck_data.libra_evaluations.emplace_back(libra_evaluation); + for (auto univariate : zk_sumcheck_data.libra_univariates) { + univariate *= FF(1) / zk_sumcheck_data.libra_challenge; + } + }; + } + + void update_zk_sumcheck_data(ZKSumcheckData& zk_sumcheck_data, FF round_challenge, size_t round_idx) + { + update_libra_data(zk_sumcheck_data, round_challenge, round_idx); + update_masking_terms_evaluations(zk_sumcheck_data, round_challenge); + } + /** + * @brief By the design of ZK Sumcheck, instead of claimed evaluations of witness polynomials \f$ P_1, \ldots, + P_{N_w} \f$, the prover sends the evaluations of the witness polynomials masked by the terms \f$ \rho_j + \sum_{i=0}^{d-1} u_i(1-u_i) \f$ for \f$ j= 1, \ldots N_w\f$. If the challenges satisfy the equation + \f$\sum_{i=0}^{d-1} u_i(1-u_i) = 0\f$, each masking term is \f$0 \f$, which could lead to the leakage of witness + information. The challenges satisfy this equation with probability \f$ \sim 1/|\mathbb{F}|\f$. + * + * @param multivariate_challenge + */ + void check_that_evals_do_not_leak_witness_data(std::vector multivariate_challenge) + { + auto masking_term = FF(0); + for (auto challenge : multivariate_challenge) { + masking_term += challenge * (FF(1) - challenge); + } + if (masking_term == FF(0)) { + throw_or_abort("The evaluations of witness polynomials are not masked, because u_0(1-u_0)+...+u_{d-1} " + "(1-u_{d-1}) = 0 "); + }; + } }; /*! \brief Implementation of the sumcheck Verifier for statements of the form \f$\sum_{\vec \ell \in \{0,1\}^d} pow_{\beta}(\vec \ell) \cdot F \left(P_1(\vec \ell),\ldots, P_N(\vec \ell) \right) = 0 \f$ for multilinear @@ -339,6 +670,9 @@ template class SumcheckVerifier { * */ using ClaimedEvaluations = typename Flavor::AllValues; + // For ZK Flavors: the verifier obtains a vector of evaluations of \f$ d \f$ univariate polynomials and uses them to + // compute full_honk_relation_purported_value + using ClaimedLibraEvaluations = typename std::vector; using Transcript = typename Flavor::Transcript; using RelationSeparator = typename Flavor::RelationSeparator; @@ -389,8 +723,21 @@ template class SumcheckVerifier { throw_or_abort("Number of variables in multivariate is 0."); } + FF libra_challenge; + FF libra_total_sum; + if constexpr (Flavor::HasZK) { + // get the claimed sum of libra masking multivariate over the hypercube + libra_total_sum = transcript->template receive_from_prover("Libra:Sum"); + // get the challenge for the ZK Sumcheck claim + libra_challenge = transcript->template get_challenge("Libra:Challenge"); + } std::vector multivariate_challenge; multivariate_challenge.reserve(multivariate_d); + // if Flavor has ZK, the target total sum is corrected by Libra total sum multiplied by the Libra + // challenge + if constexpr (Flavor::HasZK) { + round.target_total_sum += libra_total_sum * libra_challenge; + }; for (size_t round_idx = 0; round_idx < CONST_PROOF_SIZE_LOG_N; round_idx++) { // Obtain the round univariate from the transcript std::string round_univariate_label = "Sumcheck:univariate_" + std::to_string(round_idx); @@ -417,7 +764,6 @@ template class SumcheckVerifier { bool checked = round.check_sum(round_univariate); verified = verified && checked; multivariate_challenge.emplace_back(round_challenge); - round.compute_next_target_sum(round_univariate, round_challenge); pow_univariate.partially_evaluate(round_challenge); } else { @@ -425,29 +771,42 @@ template class SumcheckVerifier { } } } - + // Extract claimed evaluations of Libra univariates and compute their sum multiplied by the Libra challenge + ClaimedLibraEvaluations libra_evaluations(multivariate_d); + FF full_libra_purported_value = FF(0); + if constexpr (Flavor::HasZK) { + for (size_t idx = 0; idx < multivariate_d; idx++) { + libra_evaluations[idx] = + transcript->template receive_from_prover("libra_evaluation" + std::to_string(idx)); + full_libra_purported_value += libra_evaluations[idx]; + }; + full_libra_purported_value *= libra_challenge; + }; // Final round ClaimedEvaluations purported_evaluations; auto transcript_evaluations = transcript->template receive_from_prover>("Sumcheck:evaluations"); - for (auto [eval, transcript_eval] : zip_view(purported_evaluations.get_all(), transcript_evaluations)) { eval = transcript_eval; } - - FF full_honk_relation_purported_value = round.compute_full_honk_relation_purported_value( - purported_evaluations, relation_parameters, pow_univariate, alpha); - - bool checked = false; + // Evaluate the Honk relation at the point (u_0, ..., u_{d-1}) using claimed evaluations of prover polynomials. + // In ZK Flavors, the evaluation is corrected by full_libra_purported_value + FF full_honk_purported_value = round.compute_full_honk_relation_purported_value( + purported_evaluations, relation_parameters, pow_univariate, alpha, full_libra_purported_value); + bool final_check(false); //! [Final Verification Step] if constexpr (IsRecursiveFlavor) { - checked = (full_honk_relation_purported_value.get_value() == round.target_total_sum.get_value()); + final_check = (full_honk_purported_value.get_value() == round.target_total_sum.get_value()); } else { - checked = (full_honk_relation_purported_value == round.target_total_sum); + final_check = (full_honk_purported_value == round.target_total_sum); + } + verified = final_check && verified; + // For ZK Flavors: the evaluations of Libra univariates are included in the Sumcheck Output + if constexpr (!Flavor::HasZK) { + return SumcheckOutput{ multivariate_challenge, purported_evaluations, verified }; + } else { + return SumcheckOutput{ multivariate_challenge, purported_evaluations, libra_evaluations, verified }; } - verified = verified && checked; - //! [Final Verification Step] - return SumcheckOutput{ multivariate_challenge, purported_evaluations, verified }; }; }; } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck.test.cpp b/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck.test.cpp index ebbb6b4e191..cc38be1383a 100644 --- a/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck.test.cpp +++ b/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck.test.cpp @@ -7,189 +7,179 @@ #include "barretenberg/relations/permutation_relation.hpp" #include "barretenberg/relations/ultra_arithmetic_relation.hpp" #include "barretenberg/stdlib_circuit_builders/plookup_tables/fixed_base/fixed_base.hpp" +#include "barretenberg/stdlib_circuit_builders/ultra_flavor.hpp" +#include "barretenberg/stdlib_circuit_builders/ultra_zk_flavor.hpp" #include "barretenberg/transcript/transcript.hpp" - #include using namespace bb; namespace { -using Flavor = UltraFlavor; -using FF = typename Flavor::FF; -using Polynomial = Polynomial; -using ProverPolynomials = typename Flavor::ProverPolynomials; -using RelationSeparator = Flavor::RelationSeparator; -const size_t NUM_POLYNOMIALS = Flavor::NUM_ALL_ENTITIES; - -Polynomial random_poly(size_t size) -{ - auto poly = Polynomial(size); - for (auto& coeff : poly) { - coeff = FF::random_element(); +template class SumcheckTests : public ::testing::Test { + public: + using FF = typename Flavor::FF; + using ProverPolynomials = typename Flavor::ProverPolynomials; + using RelationSeparator = Flavor::RelationSeparator; + const size_t NUM_POLYNOMIALS = Flavor::NUM_ALL_ENTITIES; + static void SetUpTestSuite() { bb::srs::init_crs_factory("../srs_db/ignition"); } + + Polynomial random_poly(size_t size) + { + auto poly = bb::Polynomial(size); + for (auto& coeff : poly) { + coeff = FF::random_element(); + } + return poly; } - return poly; -} -ProverPolynomials construct_ultra_full_polynomials(auto& input_polynomials) -{ - ProverPolynomials full_polynomials; - for (auto [full_poly, input_poly] : zip_view(full_polynomials.get_all(), input_polynomials)) { - full_poly = input_poly.share(); + ProverPolynomials construct_ultra_full_polynomials(auto& input_polynomials) + { + ProverPolynomials full_polynomials; + for (auto [full_poly, input_poly] : zip_view(full_polynomials.get_all(), input_polynomials)) { + full_poly = input_poly.share(); + } + return full_polynomials; } - return full_polynomials; -} -} // namespace -class SumcheckTests : public ::testing::Test { - protected: - static void SetUpTestSuite() { bb::srs::init_crs_factory("../srs_db/ignition"); } -}; + void test_polynomial_normalization() + { + // TODO(#225)(Cody): We should not use real constants like this in the tests, at least not in so many of them. + const size_t multivariate_d(3); + const size_t multivariate_n(1 << multivariate_d); -TEST_F(SumcheckTests, PolynomialNormalization) -{ - // TODO(#225)(Cody): We should not use real constants like this in the tests, at least not in so many of them. - const size_t multivariate_d(3); - const size_t multivariate_n(1 << multivariate_d); - - // Randomly construct the prover polynomials that are input to Sumcheck. - // Note: ProverPolynomials are defined as spans so the polynomials they point to need to exist in memory. - std::array, NUM_POLYNOMIALS> random_polynomials; - for (auto& poly : random_polynomials) { - poly = random_poly(multivariate_n); - } - auto full_polynomials = construct_ultra_full_polynomials(random_polynomials); + // Randomly construct the prover polynomials that are input to Sumcheck. + // Note: ProverPolynomials are defined as spans so the polynomials they point to need to exist in memory. + std::vector> random_polynomials(NUM_POLYNOMIALS); + for (auto& poly : random_polynomials) { + poly = random_poly(multivariate_n); + } + auto full_polynomials = construct_ultra_full_polynomials(random_polynomials); - info(full_polynomials.w_l[0]); - info(full_polynomials.w_l[1]); - info(full_polynomials.w_l[2]); - info(full_polynomials.w_l[3]); + auto transcript = Flavor::Transcript::prover_init_empty(); - auto transcript = Flavor::Transcript::prover_init_empty(); + auto sumcheck = SumcheckProver(multivariate_n, transcript); + RelationSeparator alpha; + for (size_t idx = 0; idx < alpha.size(); idx++) { + alpha[idx] = transcript->template get_challenge("Sumcheck:alpha_" + std::to_string(idx)); + } - auto sumcheck = SumcheckProver(multivariate_n, transcript); - RelationSeparator alpha; - for (size_t idx = 0; idx < alpha.size(); idx++) { - alpha[idx] = transcript->template get_challenge("Sumcheck:alpha_" + std::to_string(idx)); - } + std::vector gate_challenges(multivariate_d); + for (size_t idx = 0; idx < multivariate_d; idx++) { + gate_challenges[idx] = + transcript->template get_challenge("Sumcheck:gate_challenge_" + std::to_string(idx)); + } + auto output = sumcheck.prove(full_polynomials, {}, alpha, gate_challenges); + + FF u_0 = output.challenge[0]; + FF u_1 = output.challenge[1]; + FF u_2 = output.challenge[2]; + + /* sumcheck.prove() terminates with sumcheck.multivariates.folded_polynoimals as an array such that + * sumcheck.multivariates.folded_polynoimals[i][0] is the evaluatioin of the i'th multivariate at the vector of + challenges u_i. What does this mean? + + Here we show that if the multivariate is F(X0, X1, X2) defined as above, then what we get is F(u0, u1, u2) and + not, say F(u2, u1, u0). This is in accordance with Adrian's thesis (cf page 9). + */ + + // Get the values of the Lagrange basis polys L_i defined + // by: L_i(v) = 1 if i = v, 0 otherwise, for v from 0 to 7. + FF one{ 1 }; + // clang-format off + FF l_0 = (one - u_0) * (one - u_1) * (one - u_2); + FF l_1 = (u_0) * (one - u_1) * (one - u_2); + FF l_2 = (one - u_0) * (u_1) * (one - u_2); + FF l_3 = (u_0) * (u_1) * (one - u_2); + FF l_4 = (one - u_0) * (one - u_1) * (u_2); + FF l_5 = (u_0) * (one - u_1) * (u_2); + FF l_6 = (one - u_0) * (u_1) * (u_2); + FF l_7 = (u_0) * (u_1) * (u_2); + // clang-format on + FF hand_computed_value; + for (auto [full_poly, partial_eval_poly] : + zip_view(full_polynomials.get_all(), sumcheck.partially_evaluated_polynomials.get_all())) { + // full_polynomials[0][0] = w_l[0], full_polynomials[1][1] = w_r[1], and so on. + hand_computed_value = l_0 * full_poly[0] + l_1 * full_poly[1] + l_2 * full_poly[2] + l_3 * full_poly[3] + + l_4 * full_poly[4] + l_5 * full_poly[5] + l_6 * full_poly[6] + l_7 * full_poly[7]; + EXPECT_EQ(hand_computed_value, partial_eval_poly[0]); + } - std::vector gate_challenges(multivariate_d); - for (size_t idx = 0; idx < multivariate_d; idx++) { - gate_challenges[idx] = transcript->template get_challenge("Sumcheck:gate_challenge_" + std::to_string(idx)); - } - auto output = sumcheck.prove(full_polynomials, {}, alpha, gate_challenges); - - FF u_0 = output.challenge[0]; - FF u_1 = output.challenge[1]; - FF u_2 = output.challenge[2]; - - /* sumcheck.prove() terminates with sumcheck.multivariates.folded_polynoimals as an array such that - * sumcheck.multivariates.folded_polynoimals[i][0] is the evaluatioin of the i'th multivariate at the vector of - challenges u_i. What does this mean? - - Here we show that if the multivariate is F(X0, X1, X2) defined as above, then what we get is F(u0, u1, u2) and - not, say F(u2, u1, u0). This is in accordance with Adrian's thesis (cf page 9). - */ - - // Get the values of the Lagrange basis polys L_i defined - // by: L_i(v) = 1 if i = v, 0 otherwise, for v from 0 to 7. - FF one{ 1 }; - // clang-format off - FF l_0 = (one - u_0) * (one - u_1) * (one - u_2); - FF l_1 = ( u_0) * (one - u_1) * (one - u_2); - FF l_2 = (one - u_0) * ( u_1) * (one - u_2); - FF l_3 = ( u_0) * ( u_1) * (one - u_2); - FF l_4 = (one - u_0) * (one - u_1) * ( u_2); - FF l_5 = ( u_0) * (one - u_1) * ( u_2); - FF l_6 = (one - u_0) * ( u_1) * ( u_2); - FF l_7 = ( u_0) * ( u_1) * ( u_2); - // clang-format on - FF hand_computed_value; - for (auto [full_poly, partial_eval_poly] : - zip_view(full_polynomials.get_all(), sumcheck.partially_evaluated_polynomials.get_all())) { - // full_polynomials[0][0] = w_l[0], full_polynomials[1][1] = w_r[1], and so on. - hand_computed_value = l_0 * full_poly[0] + l_1 * full_poly[1] + l_2 * full_poly[2] + l_3 * full_poly[3] + - l_4 * full_poly[4] + l_5 * full_poly[5] + l_6 * full_poly[6] + l_7 * full_poly[7]; - EXPECT_EQ(hand_computed_value, partial_eval_poly[0]); + // We can also check the correctness of the multilinear evaluations produced by Sumcheck by directly evaluating + // the full polynomials at challenge u via the evaluate_mle() function + std::vector u_challenge = { u_0, u_1, u_2 }; + for (auto [full_poly, claimed_eval] : + zip_view(full_polynomials.get_all(), output.claimed_evaluations.get_all())) { + Polynomial poly(full_poly); + auto v_expected = poly.evaluate_mle(u_challenge); + EXPECT_EQ(v_expected, claimed_eval); + } } - // We can also check the correctness of the multilinear evaluations produced by Sumcheck by directly evaluating the - // full polynomials at challenge u via the evaluate_mle() function - std::vector u_challenge = { u_0, u_1, u_2 }; - for (auto [full_poly, claimed_eval] : zip_view(full_polynomials.get_all(), output.claimed_evaluations.get_all())) { - bb::Polynomial poly(full_poly); - auto v_expected = poly.evaluate_mle(u_challenge); - EXPECT_EQ(v_expected, claimed_eval); - } -} + void test_prover() + { + const size_t multivariate_d(2); + const size_t multivariate_n(1 << multivariate_d); -TEST_F(SumcheckTests, Prover) -{ - const size_t multivariate_d(2); - const size_t multivariate_n(1 << multivariate_d); - - // Randomly construct the prover polynomials that are input to Sumcheck. - // Note: ProverPolynomials are defined as spans so the polynomials they point to need to exist in memory. - std::array, NUM_POLYNOMIALS> random_polynomials; - for (auto& poly : random_polynomials) { - poly = random_poly(multivariate_n); - } - auto full_polynomials = construct_ultra_full_polynomials(random_polynomials); + // Randomly construct the prover polynomials that are input to Sumcheck. + // Note: ProverPolynomials are defined as spans so the polynomials they point to need to exist in memory. + std::vector> random_polynomials(NUM_POLYNOMIALS); + for (auto& poly : random_polynomials) { + poly = random_poly(multivariate_n); + } + auto full_polynomials = construct_ultra_full_polynomials(random_polynomials); - auto transcript = Flavor::Transcript::prover_init_empty(); + auto transcript = Flavor::Transcript::prover_init_empty(); - auto sumcheck = SumcheckProver(multivariate_n, transcript); + auto sumcheck = SumcheckProver(multivariate_n, transcript); - RelationSeparator alpha; - for (size_t idx = 0; idx < alpha.size(); idx++) { - alpha[idx] = transcript->template get_challenge("Sumcheck:alpha_" + std::to_string(idx)); - } + RelationSeparator alpha; + for (size_t idx = 0; idx < alpha.size(); idx++) { + alpha[idx] = transcript->template get_challenge("Sumcheck:alpha_" + std::to_string(idx)); + } - std::vector gate_challenges(multivariate_d); - for (size_t idx = 0; idx < gate_challenges.size(); idx++) { - gate_challenges[idx] = transcript->template get_challenge("Sumcheck:gate_challenge_" + std::to_string(idx)); - } - auto output = sumcheck.prove(full_polynomials, {}, alpha, gate_challenges); - FF u_0 = output.challenge[0]; - FF u_1 = output.challenge[1]; - std::vector expected_values; - for (auto& polynomial_ptr : full_polynomials.get_all()) { - auto& polynomial = polynomial_ptr; - // using knowledge of inputs here to derive the evaluation - FF expected_lo = polynomial[0] * (FF(1) - u_0) + polynomial[1] * u_0; - expected_lo *= (FF(1) - u_1); - FF expected_hi = polynomial[2] * (FF(1) - u_0) + polynomial[3] * u_0; - expected_hi *= u_1; - expected_values.emplace_back(expected_lo + expected_hi); - } + std::vector gate_challenges(multivariate_d); + for (size_t idx = 0; idx < gate_challenges.size(); idx++) { + gate_challenges[idx] = + transcript->template get_challenge("Sumcheck:gate_challenge_" + std::to_string(idx)); + } + auto output = sumcheck.prove(full_polynomials, {}, alpha, gate_challenges); + FF u_0 = output.challenge[0]; + FF u_1 = output.challenge[1]; + std::vector expected_values; + for (auto& polynomial_ptr : full_polynomials.get_all()) { + auto& polynomial = polynomial_ptr; + // using knowledge of inputs here to derive the evaluation + FF expected_lo = polynomial[0] * (FF(1) - u_0) + polynomial[1] * u_0; + expected_lo *= (FF(1) - u_1); + FF expected_hi = polynomial[2] * (FF(1) - u_0) + polynomial[3] * u_0; + expected_hi *= u_1; + expected_values.emplace_back(expected_lo + expected_hi); + } - for (auto [eval, expected] : zip_view(output.claimed_evaluations.get_all(), expected_values)) { - eval = expected; + for (auto [eval, expected] : zip_view(output.claimed_evaluations.get_all(), expected_values)) { + eval = expected; + } } -} -// TODO(#225): make the inputs to this test more interesting, e.g. non-trivial permutations -TEST_F(SumcheckTests, ProverAndVerifierSimple) -{ - auto run_test = [](bool expect_verified) { + // TODO(#225): make the inputs to this test more interesting, e.g. non-trivial permutations + void test_prover_verifier_flow() + { const size_t multivariate_d(2); const size_t multivariate_n(1 << multivariate_d); // Construct prover polynomials where each is the zero polynomial. // Note: ProverPolynomials are defined as spans so the polynomials they point to need to exist in memory. - std::array, NUM_POLYNOMIALS> zero_polynomials; + std::vector> zero_polynomials(NUM_POLYNOMIALS); for (auto& poly : zero_polynomials) { poly = bb::Polynomial(multivariate_n); } auto full_polynomials = construct_ultra_full_polynomials(zero_polynomials); // Add some non-trivial values to certain polynomials so that the arithmetic relation will have non-trivial - // contribution. Note: since all other polynomials are set to 0, all other relations are trivially satisfied. + // contribution. Note: since all other polynomials are set to 0, all other relations are trivially + // satisfied. std::array w_l; - if (expect_verified) { - w_l = { 0, 1, 2, 0 }; - } else { - w_l = { 0, 0, 2, 0 }; - } + w_l = { 0, 1, 2, 0 }; std::array w_r = { 0, 1, 2, 0 }; std::array w_o = { 0, 2, 4, 0 }; std::array w_4 = { 0, 0, 0, 0 }; @@ -218,7 +208,87 @@ TEST_F(SumcheckTests, ProverAndVerifierSimple) .gamma = FF::random_element(), .public_input_delta = FF::one(), }; + auto prover_transcript = Flavor::Transcript::prover_init_empty(); + auto sumcheck_prover = SumcheckProver(multivariate_n, prover_transcript); + + RelationSeparator prover_alpha; + for (size_t idx = 0; idx < prover_alpha.size(); idx++) { + prover_alpha[idx] = prover_transcript->template get_challenge("Sumcheck:alpha_" + std::to_string(idx)); + } + std::vector prover_gate_challenges(multivariate_d); + for (size_t idx = 0; idx < multivariate_d; idx++) { + prover_gate_challenges[idx] = + prover_transcript->template get_challenge("Sumcheck:gate_challenge_" + std::to_string(idx)); + } + auto output = + sumcheck_prover.prove(full_polynomials, relation_parameters, prover_alpha, prover_gate_challenges); + + auto verifier_transcript = Flavor::Transcript::verifier_init_empty(prover_transcript); + + auto sumcheck_verifier = SumcheckVerifier(multivariate_d, verifier_transcript); + RelationSeparator verifier_alpha; + for (size_t idx = 0; idx < verifier_alpha.size(); idx++) { + verifier_alpha[idx] = + verifier_transcript->template get_challenge("Sumcheck:alpha_" + std::to_string(idx)); + } + std::vector verifier_gate_challenges(multivariate_d); + for (size_t idx = 0; idx < multivariate_d; idx++) { + verifier_gate_challenges[idx] = + verifier_transcript->template get_challenge("Sumcheck:gate_challenge_" + std::to_string(idx)); + } + auto verifier_output = sumcheck_verifier.verify(relation_parameters, verifier_alpha, verifier_gate_challenges); + + auto verified = verifier_output.verified.value(); + + EXPECT_EQ(verified, true); + }; + + void test_failure_prover_verifier_flow() + { + const size_t multivariate_d(2); + const size_t multivariate_n(1 << multivariate_d); + + // Construct prover polynomials where each is the zero polynomial. + // Note: ProverPolynomials are defined as spans so the polynomials they point to need to exist in memory. + std::vector> zero_polynomials(NUM_POLYNOMIALS); + for (auto& poly : zero_polynomials) { + poly = bb::Polynomial(multivariate_n); + } + auto full_polynomials = construct_ultra_full_polynomials(zero_polynomials); + // Add some non-trivial values to certain polynomials so that the arithmetic relation will have non-trivial + // contribution. Note: since all other polynomials are set to 0, all other relations are trivially + // satisfied. + std::array w_l; + w_l = { 0, 0, 2, 0 }; // this witness value makes the circuit from previous test invalid + std::array w_r = { 0, 1, 2, 0 }; + std::array w_o = { 0, 2, 4, 0 }; + std::array w_4 = { 0, 0, 0, 0 }; + std::array q_m = { 0, 0, 1, 0 }; + std::array q_l = { 0, 1, 0, 0 }; + std::array q_r = { 0, 1, 0, 0 }; + std::array q_o = { 0, -1, -1, 0 }; + std::array q_c = { 0, 0, 0, 0 }; + std::array q_arith = { 0, 1, 1, 0 }; + // Setting all of these to 0 ensures the GrandProductRelation is satisfied + + full_polynomials.w_l = bb::Polynomial(w_l); + full_polynomials.w_r = bb::Polynomial(w_r); + full_polynomials.w_o = bb::Polynomial(w_o); + full_polynomials.w_4 = bb::Polynomial(w_4); + full_polynomials.q_m = bb::Polynomial(q_m); + full_polynomials.q_l = bb::Polynomial(q_l); + full_polynomials.q_r = bb::Polynomial(q_r); + full_polynomials.q_o = bb::Polynomial(q_o); + full_polynomials.q_c = bb::Polynomial(q_c); + full_polynomials.q_arith = bb::Polynomial(q_arith); + + // Set aribitrary random relation parameters + RelationParameters relation_parameters{ + .beta = FF::random_element(), + .gamma = FF::random_element(), + .public_input_delta = FF::one(), + }; auto prover_transcript = Flavor::Transcript::prover_init_empty(); auto sumcheck_prover = SumcheckProver(multivariate_n, prover_transcript); @@ -231,7 +301,8 @@ TEST_F(SumcheckTests, ProverAndVerifierSimple) prover_gate_challenges[idx] = prover_transcript->template get_challenge("Sumcheck:gate_challenge_" + std::to_string(idx)); } - auto output = sumcheck_prover.prove(full_polynomials, {}, prover_alpha, prover_gate_challenges); + auto output = + sumcheck_prover.prove(full_polynomials, relation_parameters, prover_alpha, prover_gate_challenges); auto verifier_transcript = Flavor::Transcript::verifier_init_empty(prover_transcript); @@ -250,9 +321,38 @@ TEST_F(SumcheckTests, ProverAndVerifierSimple) auto verified = verifier_output.verified.value(); - EXPECT_EQ(verified, expect_verified); + EXPECT_EQ(verified, false); }; +}; + +// Define the FlavorTypes +using FlavorTypes = testing::Types; - run_test(/* expect_verified=*/true); - run_test(/* expect_verified=*/false); +TYPED_TEST_SUITE(SumcheckTests, FlavorTypes); + +#define SKIP_IF_ZK() \ + if (std::is_same::value) { \ + GTEST_SKIP() << "Skipping test for UltraFlavorWithZK"; \ + } + +TYPED_TEST(SumcheckTests, PolynomialNormalization) +{ + SKIP_IF_ZK(); + this->test_polynomial_normalization(); } +// Test the prover +TYPED_TEST(SumcheckTests, Prover) +{ + this->test_prover(); +} +// Tests the prover-verifier flow +TYPED_TEST(SumcheckTests, ProverAndVerifierSimple) +{ + this->test_prover_verifier_flow(); +} +// This tests is fed an invalid circuit and checks that the verifier would output false. +TYPED_TEST(SumcheckTests, ProverAndVerifierSimpleFailure) +{ + this->test_failure_prover_verifier_flow(); +} +} // namespace diff --git a/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck_output.hpp b/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck_output.hpp index 9abd4a2feb0..da4b98ce43b 100644 --- a/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck_output.hpp +++ b/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck_output.hpp @@ -1,5 +1,5 @@ #pragma once - +#include "barretenberg/flavor/flavor.hpp" #include #include #include @@ -11,15 +11,34 @@ namespace bb { * =(u_0,\ldots, u_{d-1})\f$. These are computed by \ref bb::SumcheckProver< Flavor > "Sumcheck Prover" and need to be * checked using Zeromorph. */ -template struct SumcheckOutput { +template struct SumcheckOutput { using FF = typename Flavor::FF; using ClaimedEvaluations = typename Flavor::AllValues; // \f$ \vec u = (u_0, ..., u_{d-1}) \f$ std::vector challenge; - // Evaluations in \f$ \vec u \f$ of the polynomials used in Sumcheck + // Evaluations at \f$ \vec u \f$ of the polynomials used in Sumcheck ClaimedEvaluations claimed_evaluations; // Whether or not the evaluations of multilinear polynomials \f$ P_1, \ldots, P_N \f$ and final Sumcheck evaluation // have been confirmed std::optional verified = false; // optional b/c this struct is shared by the Prover/Verifier }; +/** + * @brief A modification of SumcheckOutput required by ZK Flavors where a vector of evaluations of Libra univariates is + * included. + * + * @tparam Flavor + */ +template struct SumcheckOutput>> { + using FF = typename Flavor::FF; + using ClaimedEvaluations = typename Flavor::AllValues; + // \f$ \vec u = (u_0, ..., u_{d-1}) \f$ + std::vector challenge; + // Evaluations at \f$ \vec u \f$ of the polynomials used in Sumcheck + ClaimedEvaluations claimed_evaluations; + // Include ClaimedLibraEvaluations conditioned on FlavorHasZK concept + std::vector claimed_libra_evaluations; + // Whether or not the evaluations of multilinear polynomials \f$ P_1, \ldots, P_N \f$ and final Sumcheck evaluation + // have been confirmed + std::optional verified = false; // Optional b/c this struct is shared by the Prover/Verifier +}; } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck_round.hpp b/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck_round.hpp index 6b014b66378..14ccd07c72f 100644 --- a/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck_round.hpp +++ b/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck_round.hpp @@ -6,6 +6,7 @@ #include "barretenberg/relations/relation_types.hpp" #include "barretenberg/relations/utils.hpp" #include "barretenberg/stdlib/primitives/bool/bool.hpp" +#include "zk_sumcheck_data.hpp" namespace bb { @@ -59,9 +60,8 @@ template class SumcheckProverRound { * "MAX_PARTIAL_RELATION_LENGTH + 1". */ static constexpr size_t BATCHED_RELATION_PARTIAL_LENGTH = Flavor::BATCHED_RELATION_PARTIAL_LENGTH; - + using SumcheckRoundUnivariate = bb::Univariate; SumcheckTupleOfTuplesOfUnivariates univariate_accumulators; - // Prover constructor SumcheckProverRound(size_t initial_round_size) : round_size(initial_round_size) @@ -87,7 +87,9 @@ template class SumcheckProverRound { input in the first round, or from the \ref multivariates table. Using general method \ref bb::Univariate::extend_to "extend_to", the evaluations of these polynomials are extended from the domain \f$ \{0,1\} \f$ to the domain \f$ \{0,\ldots, D\} \f$ required for the computation of the round univariate. - + * In the case when witness polynomials are masked (ZK Flavors), this method has to distinguish between witness and + * non-witness polynomials. The witness univariates obtained from witness multilinears are corrected by a masking + * quadratic term extended to the same length MAX_PARTIAL_RELATION_LENGTH. * Should only be called externally with relation_idx equal to 0. * In practice, #multivariates is either ProverPolynomials or PartiallyEvaluatedMultivariates. * @@ -98,13 +100,33 @@ template class SumcheckProverRound { */ template void extend_edges(ExtendedEdges& extended_edges, - const ProverPolynomialsOrPartiallyEvaluatedMultivariates& multivariates, - size_t edge_idx) + ProverPolynomialsOrPartiallyEvaluatedMultivariates& multivariates, + size_t edge_idx, + std::optional> zk_sumcheck_data = std::nullopt) { - for (auto [extended_edge, multivariate] : zip_view(extended_edges.get_all(), multivariates.get_all())) { - bb::Univariate edge({ multivariate[edge_idx], multivariate[edge_idx + 1] }); - extended_edge = edge.template extend_to(); - } + + if constexpr (!Flavor::HasZK) { + for (auto [extended_edge, multivariate] : zip_view(extended_edges.get_all(), multivariates.get_all())) { + bb::Univariate edge({ multivariate[edge_idx], multivariate[edge_idx + 1] }); + extended_edge = edge.template extend_to(); + } + } else { + // extend edges of witness polynomials and add correcting terms + for (auto [extended_edge, multivariate, masking_univariate] : + zip_view(extended_edges.get_all_witnesses(), + multivariates.get_all_witnesses(), + zk_sumcheck_data.value().masking_terms_evaluations)) { + bb::Univariate edge({ multivariate[edge_idx], multivariate[edge_idx + 1] }); + extended_edge = edge.template extend_to(); + extended_edge += masking_univariate; + }; + // extend edges of public polynomials + for (auto [extended_edge, multivariate] : + zip_view(extended_edges.get_non_witnesses(), multivariates.get_non_witnesses())) { + bb::Univariate edge({ multivariate[edge_idx], multivariate[edge_idx + 1] }); + extended_edge = edge.template extend_to(); + }; + }; } /** @@ -130,11 +152,13 @@ template class SumcheckProverRound { method \ref extend_and_batch_univariates "extend and batch univariates". */ template - bb::Univariate compute_univariate( + SumcheckRoundUnivariate compute_univariate( + const size_t round_idx, ProverPolynomialsOrPartiallyEvaluatedMultivariates& polynomials, const bb::RelationParameters& relation_parameters, const bb::PowPolynomial& pow_polynomial, - const RelationSeparator alpha) + const RelationSeparator alpha, + std::optional> zk_sumcheck_data = std::nullopt) // only submitted when Flavor HasZK { BB_OP_COUNT_TIME(); @@ -162,8 +186,11 @@ template class SumcheckProverRound { size_t end = (thread_idx + 1) * iterations_per_thread; for (size_t edge_idx = start; edge_idx < end; edge_idx += 2) { - extend_edges(extended_edges[thread_idx], polynomials, edge_idx); - + if constexpr (!Flavor::HasZK) { + extend_edges(extended_edges[thread_idx], polynomials, edge_idx); + } else { + extend_edges(extended_edges[thread_idx], polynomials, edge_idx, zk_sumcheck_data); + } // Compute the \f$ \ell \f$-th edge's univariate contribution, // scale it by the corresponding \f$ pow_{\beta} \f$ contribution and add it to the accumulators for \f$ // \tilde{S}^i(X_i) \f$. If \f$ \ell \f$'s binary representation is given by \f$ (\ell_{i+1},\ldots, @@ -180,10 +207,19 @@ template class SumcheckProverRound { for (auto& accumulators : thread_univariate_accumulators) { Utils::add_nested_tuples(univariate_accumulators, accumulators); } - + // For ZK Flavors: The evaluations of the round univariates are masked by the evaluations of Libra univariates + if constexpr (Flavor::HasZK) { + auto libra_round_univariate = compute_libra_round_univariate(zk_sumcheck_data.value(), round_idx); + // Batch the univariate contributions from each sub-relation to obtain the round univariate + auto round_univariate = + batch_over_relations(univariate_accumulators, alpha, pow_polynomial); + // Mask the round univariate + return round_univariate + libra_round_univariate; + } // Batch the univariate contributions from each sub-relation to obtain the round univariate - return batch_over_relations>( - univariate_accumulators, alpha, pow_polynomial); + else { + return batch_over_relations(univariate_accumulators, alpha, pow_polynomial); + } } /** @@ -263,6 +299,32 @@ template class SumcheckProverRound { Utils::apply_to_tuple_of_tuples(tuple, extend_and_sum); } + /** + * @brief Compute Libra round univariate expressed given by the formula + \f{align}{ + \texttt{libra_round_univariate}_i(k) = + \rho \cdot 2^{d-1-i} \left(\sum_{j = 0}^{i-1} g_j(u_{j}) + g_{i,k}+ + \sum_{j=i+1}^{d-1}\left(g_{j,0}+g_{j,1}\right)\right) + = \texttt{libra_univariates}_{i}(k) + \texttt{libra_running_sum} + \f}. + * + * @param zk_sumcheck_data + * @param round_idx + */ + static SumcheckRoundUnivariate compute_libra_round_univariate(ZKSumcheckData zk_sumcheck_data, + size_t round_idx) + { + SumcheckRoundUnivariate libra_round_univariate; + // select the i'th column of Libra book-keeping table + auto current_column = zk_sumcheck_data.libra_univariates[round_idx]; + // the evaluation of Libra round univariate at k=0...D are equal to \f$\texttt{libra_univariates}_{i}(k)\f$ + // corrected by the Libra running sum + for (size_t idx = 0; idx < BATCHED_RELATION_PARTIAL_LENGTH; ++idx) { + libra_round_univariate.value_at(idx) = current_column.value_at(idx) + zk_sumcheck_data.libra_running_sum; + }; + return libra_round_univariate; + } + private: /** * @brief In Round \f$ i \f$, for a given point \f$ \vec \ell \in \{0,1\}^{d-1 - i}\f$, calculate the contribution @@ -295,7 +357,6 @@ template class SumcheckProverRound { const FF& scaling_factor) { using Relation = std::tuple_element_t; - // Check if the relation is skippable to speed up accumulation if constexpr (!isSkippable) { // If not, accumulate normally @@ -310,7 +371,6 @@ template class SumcheckProverRound { scaling_factor); } } - // Repeat for the next relation. if constexpr (relation_idx + 1 < NUM_RELATIONS) { accumulate_relation_univariates( @@ -340,6 +400,7 @@ template class SumcheckVerifierRound { public: using FF = typename Flavor::FF; using ClaimedEvaluations = typename Flavor::AllValues; + using ClaimedLibraEvaluations = typename std::vector; bool round_failed = false; /** @@ -352,6 +413,7 @@ template class SumcheckVerifierRound { * MAX_PARTIAL_RELATION_LENGTH "MAX_PARTIAL_RELATION_LENGTH + 1". */ static constexpr size_t BATCHED_RELATION_PARTIAL_LENGTH = Flavor::BATCHED_RELATION_PARTIAL_LENGTH; + using SumcheckRoundUnivariate = bb::Univariate; FF target_total_sum = 0; @@ -370,7 +432,7 @@ template class SumcheckVerifierRound { * @param univariate Round univariate \f$\tilde{S}^{i}\f$ represented by its evaluations over \f$0,\ldots,D\f$. * */ - bool check_sum(bb::Univariate& univariate) + bool check_sum(SumcheckRoundUnivariate& univariate) { FF total_sum = univariate.value_at(0) + univariate.value_at(1); // TODO(#673): Conditionals like this can go away once native verification is is just recursive verification @@ -437,7 +499,7 @@ template class SumcheckVerifierRound { * @param round_challenge \f$ u_i\f$ * @return FF \f$ \sigma_{i+1} = \tilde{S}^i(u_i)\f$ */ - FF compute_next_target_sum(bb::Univariate& univariate, FF& round_challenge) + FF compute_next_target_sum(SumcheckRoundUnivariate& univariate, FF& round_challenge) { // Evaluate \f$\tilde{S}^{i}(u_{i}) \f$ target_total_sum = univariate.evaluate(round_challenge); @@ -473,7 +535,8 @@ template class SumcheckVerifierRound { FF compute_full_honk_relation_purported_value(ClaimedEvaluations purported_evaluations, const bb::RelationParameters& relation_parameters, const bb::PowPolynomial& pow_polynomial, - const RelationSeparator alpha) + const RelationSeparator alpha, + std::optional full_libra_purported_value = std::nullopt) { // The verifier should never skip computation of contributions from any relation Utils::template accumulate_relation_evaluations_without_skipping<>( @@ -482,6 +545,9 @@ template class SumcheckVerifierRound { FF running_challenge{ 1 }; FF output{ 0 }; Utils::scale_and_batch_elements(relation_evaluations, alpha, running_challenge, output); + if constexpr (Flavor::HasZK) { + output += full_libra_purported_value.value(); + }; return output; } }; diff --git a/barretenberg/cpp/src/barretenberg/sumcheck/zk_sumcheck_data.hpp b/barretenberg/cpp/src/barretenberg/sumcheck/zk_sumcheck_data.hpp new file mode 100644 index 00000000000..f438ab37916 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/sumcheck/zk_sumcheck_data.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include +#include +#include + +namespace bb { + +/** + * @brief This structure is created to contain various polynomials and constants required by ZK Sumcheck. + * + */ +template struct ZKSumcheckData { + using FF = typename Flavor::FF; + /** + * @brief The total algebraic degree of the Sumcheck relation \f$ F \f$ as a polynomial in Prover Polynomials + * \f$P_1,\ldots, P_N\f$. + */ + static constexpr size_t MAX_PARTIAL_RELATION_LENGTH = Flavor::MAX_PARTIAL_RELATION_LENGTH; + // The number of all witnesses including shifts and derived witnesses from flavors that have ZK, + // otherwise, set this constant to 0. + /** + * @brief The total algebraic degree of the Sumcheck relation \f$ F \f$ as a polynomial in Prover Polynomials + * \f$P_1,\ldots, P_N\f$ incremented by 1, i.e. it is equal \ref MAX_PARTIAL_RELATION_LENGTH + * "MAX_PARTIAL_RELATION_LENGTH + 1". + */ + static constexpr size_t BATCHED_RELATION_PARTIAL_LENGTH = Flavor::BATCHED_RELATION_PARTIAL_LENGTH; + // Initialize the length of the array of evaluation masking scalars as 0 for non-ZK Flavors and as + // NUM_ALL_WITNESS_ENTITIES for ZK FLavors + static constexpr size_t MASKING_SCALARS_LENGTH = Flavor::HasZK ? Flavor::NUM_ALL_WITNESS_ENTITIES : 0; + // Array of random scalars used to hide the witness info from leaking through the claimed evaluations + using EvalMaskingScalars = std::array; + // Auxiliary table that represents the evaluations of quadratic polynomials r_j * X(1-X) at 0,..., + // MAX_PARTIAL_RELATION_LENGTH - 1 + using EvaluationMaskingTable = std::array, MASKING_SCALARS_LENGTH>; + // The size of the LibraUnivariates. We ensure that they do not take extra space when Flavor runs non-ZK + // Sumcheck. + static constexpr size_t LIBRA_UNIVARIATES_LENGTH = Flavor::HasZK ? Flavor::BATCHED_RELATION_PARTIAL_LENGTH : 0; + // Container for the Libra Univariates. Their number depends on the size of the circuit. + using LibraUnivariates = std::vector>; + // Container for the evaluations of Libra Univariates that have to be proven. + using ClaimedLibraEvaluations = std::vector; + + EvalMaskingScalars eval_masking_scalars; + EvaluationMaskingTable masking_terms_evaluations; + LibraUnivariates libra_univariates; + FF libra_scaling_factor{ 1 }; + FF libra_challenge; + FF libra_running_sum; + ClaimedLibraEvaluations libra_evaluations; +}; + +} // namespace bb