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