Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
gemini_impl.hpp
Go to the documentation of this file.
1// === AUDIT STATUS ===
2// internal: { status: Planned, auditors: [Khashayar], commit: }
3// external_1: { status: not started, auditors: [], commit: }
4// external_2: { status: not started, auditors: [], commit: }
5// =====================
6
7#pragma once
11#include "gemini.hpp"
12
49namespace bb {
50template <typename Curve>
51template <typename Transcript>
53 size_t circuit_size,
54 PolynomialBatcher& polynomial_batcher,
55 std::span<Fr> multilinear_challenge,
56 const CommitmentKey<Curve>& commitment_key,
57 const std::shared_ptr<Transcript>& transcript,
58 bool has_zk)
59{
60 BB_BENCH_NAME("GeminiProver::prove");
61 // To achieve fixed proof size in Ultra and Mega, the multilinear opening challenge is be padded to a fixed size.
62 const size_t virtual_log_n = multilinear_challenge.size();
63 const size_t log_n = numeric::get_msb(circuit_size);
64
65 // Get the batching challenge
66 const Fr rho = transcript->template get_challenge<Fr>("rho");
67
68 Polynomial A_0 = polynomial_batcher.compute_batched(rho);
69
70 // Construct the d-1 Gemini foldings of A₀(X)
71 std::vector<Polynomial> fold_polynomials = compute_fold_polynomials(log_n, multilinear_challenge, A_0);
72
73 // Commit to the virtual_log_n - 1 fold polynomials. When virtual_log_n > log_n, the trailing fold polynomials
74 // for the virtual rounds were appended as constant polynomials by compute_fold_polynomials; their commitments
75 // contribute nothing to the Shplonk quotient and are zeroed by the verifier.
76 for (size_t l = 0; l < virtual_log_n - 1; l++) {
77 std::string label = "Gemini:FOLD_" + std::to_string(l + 1);
78 transcript->send_to_verifier(label, commitment_key.commit(fold_polynomials[l]));
79 }
80 const Fr r_challenge = transcript->template get_challenge<Fr>("Gemini:r");
81
82 const bool gemini_challenge_in_small_subgroup = (has_zk) && (r_challenge.pow(Curve::SUBGROUP_SIZE) == Fr(1));
83
84 // If Gemini evaluation challenge lands in the multiplicative subgroup used by SmallSubgroupIPA protocol, the
85 // evaluations of prover polynomials at this challenge would leak witness data.
86 // TODO(https://github.com/AztecProtocol/barretenberg/issues/1194). Handle edge cases in PCS
87 if (gemini_challenge_in_small_subgroup) {
88 throw_or_abort("Gemini evaluation challenge is in the SmallSubgroup.");
89 }
90
91 // Compute polynomials A₀₊(X) = F(X) + G(X)/r and A₀₋(X) = F(X) - G(X)/r
92 auto [A_0_pos, A_0_neg] = polynomial_batcher.compute_partially_evaluated_batch_polynomials(r_challenge);
93 // Construct claims for the d + 1 univariate evaluations A₀₊(r), A₀₋(-r), and Foldₗ(−r^{2ˡ}), l = 1, ..., d-1
94 std::vector<Claim> claims = construct_univariate_opening_claims(
95 virtual_log_n, std::move(A_0_pos), std::move(A_0_neg), std::move(fold_polynomials), r_challenge);
96
97 for (size_t l = 1; l <= virtual_log_n; l++) {
98 std::string label = "Gemini:a_" + std::to_string(l);
99 transcript->send_to_verifier(label, claims[l].opening_pair.evaluation);
100 }
101
102 return claims;
103};
104
112template <typename Curve>
114 const size_t log_n, std::span<const Fr> multilinear_challenge, const Polynomial& A_0)
115{
116 BB_BENCH_NAME("Gemini::compute_fold_polynomials");
117 BB_ASSERT_GTE(log_n, size_t(2), "Gemini folding requires at least 4-element polynomials");
118 const size_t virtual_log_n = multilinear_challenge.size();
119
120 // Cost per iteration: 1 subtraction + 1 multiplication + 1 addition
121 constexpr size_t fold_iteration_cost =
123
124 // Track the actual data extent through fold rounds. Only non-zero coefficients need folding;
125 // beyond this extent, all values are zero and contribute nothing.
126 // At minimum, the disabled head region must be covered (masking values live at rows 1..3).
127 size_t actual_size = std::max(A_0.end_index(), static_cast<size_t>(NUM_DISABLED_ROWS_IN_SUMCHECK));
128
129 // Reserve space for the virtual_log_n - 1 Fold polynomials, the foldings of the full batched polynomial A₀
130 std::vector<Polynomial> fold_polynomials;
131 fold_polynomials.reserve(virtual_log_n - 1);
132 for (size_t l = 0; l < log_n - 1; ++l) {
133 const size_t fold_size = (actual_size + 1) / 2;
134
135 // A_l_fold = Aₗ₊₁(X) = (1-uₗ)⋅even(Aₗ)(X) + uₗ⋅odd(Aₗ)(X)
136 fold_polynomials.emplace_back(Polynomial(fold_size));
137 actual_size = fold_size;
138 }
139
140 // A_l = Aₗ(X) is the polynomial being folded: the batched polynomial A_0 in the first iteration,
141 // the previous fold output thereafter.
142 actual_size = A_0.end_index();
143 const Polynomial* A_l = &A_0;
144 for (size_t l = 0; l < log_n - 1; ++l) {
145 const size_t fold_size = (actual_size + 1) / 2;
146 const size_t num_pairs = actual_size / 2; // number of full even/odd pairs
147
148 // Opening point is the same for all; use zero for rounds beyond the challenge size
149 const Fr u_l = l < virtual_log_n ? multilinear_challenge[l] : Fr(0);
150
151 // A_l_fold = Aₗ₊₁(X) = (1-uₗ)⋅even(Aₗ)(X) + uₗ⋅odd(Aₗ)(X), i.e. the stride-2 fold
152 // A_l_fold[j] = A_l[2j] + u_l * (A_l[2j+1] - A_l[2j]). Each thread folds a disjoint output
153 // slice; `fold_stride2` runs the WASM SIMD bulk + scalar tail (see its definition). The output
154 // buffer is freshly allocated, so there is no aliasing with the source.
155 Polynomial& A_l_fold = fold_polynomials[l];
156 const Polynomial& source = *A_l;
158 num_pairs,
159 [&](const ThreadChunk& chunk) {
160 auto chunk_range = chunk.range(num_pairs);
161 if (chunk_range.empty()) {
162 return;
163 }
164 const size_t lo = *chunk_range.begin();
165 const size_t hi = lo + chunk_range.size();
166 fold_stride2(source, A_l_fold, lo, hi, u_l);
167 },
168 fold_iteration_cost);
169 // If odd number of coefficients, the last one has no partner (implicitly 0)
170 if (actual_size & 1) {
171 A_l_fold.at(num_pairs) = source[actual_size - 1] * (Fr(1) - u_l);
172 }
173 // set Aₗ₊₁ = Aₗ for the next iteration
174 A_l = &A_l_fold;
175 actual_size = fold_size;
176 }
177
178 // Virtual rounds (indices log_n .. virtual_log_n - 1).
179 // After real folding, the fold polynomials are constant. Since each constant polynomial evaluates to its own
180 // value at every point, (f(X) - f(x)) / (X - x) = 0, so these contribute nothing to the Shplonk quotient Q(X).
181 // On the verifier side, these constant fold polynomials contribute nothing to the Shplonk quotient.
182 const auto& last = fold_polynomials.back();
183 const Fr u_last = (log_n - 1) < virtual_log_n ? multilinear_challenge[log_n - 1] : Fr(0);
184 const Fr final_eval = last.at(0) + u_last * (last.at(1) - last.at(0));
185 Polynomial const_fold(1);
186 const_fold.at(0) = final_eval;
187 fold_polynomials.emplace_back(std::move(const_fold));
188
189 // FOLD_{log_n+1}, ..., FOLD_{d_v-1}
190 Fr tail = Fr(1);
191 for (size_t k = log_n; k < virtual_log_n - 1; ++k) {
192 tail *= (Fr(1) - multilinear_challenge[k]); // multiply by (1 - u_k)
193 Polynomial next_const(1);
194 next_const.at(0) = final_eval * tail;
195 fold_polynomials.emplace_back(std::move(next_const));
196 }
197
198 return fold_polynomials;
199};
200
222template <typename Curve>
224 const size_t log_n,
225 Polynomial&& A_0_pos,
226 Polynomial&& A_0_neg,
227 std::vector<Polynomial>&& fold_polynomials,
228 const Fr& r_challenge)
229{
230 std::vector<Claim> claims;
231 claims.reserve(log_n + 1);
232
233 // Compute evaluation of partially evaluated batch polynomial (positive) A₀₊(r)
234 Fr a_0_pos = A_0_pos.evaluate(r_challenge);
235 claims.emplace_back(Claim{ std::move(A_0_pos), { r_challenge, a_0_pos } });
236 // Compute evaluation of partially evaluated batch polynomial (negative) A₀₋(-r)
237 Fr a_0_neg = A_0_neg.evaluate(-r_challenge);
238 claims.emplace_back(Claim{ std::move(A_0_neg), { -r_challenge, a_0_neg } });
239
240 // Compute univariate opening queries rₗ = r^{2ˡ} for l = 0, 1, ..., m-1
241 std::vector<Fr> r_squares = gemini::powers_of_evaluation_challenge(r_challenge, log_n);
242
243 // Each fold polynomial Aₗ has to be opened at −r^{2ˡ} and r^{2ˡ}. To avoid storing two copies of Aₗ for l = 1,...,
244 // m-1, we use a flag that is processed by ShplonkProver.
245 const bool gemini_fold = true;
246
247 // Compute the remaining m opening pairs {−r^{2ˡ}, Aₗ(−r^{2ˡ})}, l = 1, ..., m-1.
248 for (size_t l = 0; l < log_n - 1; ++l) {
249 Fr evaluation = fold_polynomials[l].evaluate(-r_squares[l + 1]);
250 claims.emplace_back(Claim{ std::move(fold_polynomials[l]), { -r_squares[l + 1], evaluation }, gemini_fold });
251 }
252
253 return claims;
254};
255
256} // namespace bb
#define BB_ASSERT_GTE(left, right,...)
Definition assert.hpp:128
#define BB_BENCH_NAME(name)
Definition bb_bench.hpp:264
CommitmentKey object over a pairing group 𝔾₁.
Commitment commit(PolynomialSpan< const Fr > polynomial, bool has_duplicates_hint=false) const
Uses the ProverSRS to create a commitment to p(X)
Class responsible for computation of the batched multilinear polynomials required by the Gemini proto...
Definition gemini.hpp:129
Polynomial compute_batched(const Fr &challenge)
Compute batched polynomial A₀ = F + G/X as the linear combination of all polynomials to be opened,...
Definition gemini.hpp:163
std::pair< Polynomial, Polynomial > compute_partially_evaluated_batch_polynomials(const Fr &r_challenge)
Compute partially evaluated batched polynomials A₀(X, r) = A₀₊ = F + G/r, A₀(X, -r) = A₀₋ = F - G/r.
Definition gemini.hpp:206
static std::vector< Claim > prove(size_t circuit_size, PolynomialBatcher &polynomial_batcher, std::span< Fr > multilinear_challenge, const CommitmentKey< Curve > &commitment_key, const std::shared_ptr< Transcript > &transcript, bool has_zk=false)
static std::vector< Claim > construct_univariate_opening_claims(const size_t log_n, Polynomial &&A_0_pos, Polynomial &&A_0_neg, std::vector< Polynomial > &&fold_polynomials, const Fr &r_challenge)
Computes/aggragates d+1 univariate polynomial opening claims of the form {polynomial,...
typename Curve::ScalarField Fr
Definition gemini.hpp:109
static std::vector< Polynomial > compute_fold_polynomials(const size_t log_n, std::span< const Fr > multilinear_challenge, const Polynomial &A_0)
Computes d-1 fold polynomials Fold_i, i = 1, ..., d-1.
size_t end_index() const
Fr & at(size_t index)
Our mutable accessor, unlike operator[]. We abuse precedent a bit to differentiate at() and operator[...
Polynomial p and an opening pair (r,v) such that p(r) = v.
Definition claim.hpp:36
static constexpr size_t SUBGROUP_SIZE
Definition grumpkin.hpp:74
std::string label
std::vector< Fr > powers_of_evaluation_challenge(const Fr &r, const size_t num_squares)
Compute squares of folding challenge r.
Definition gemini.hpp:96
constexpr T get_msb(const T in)
Definition get_msb.hpp:50
constexpr size_t FF_ADDITION_COST
Definition thread.hpp:132
constexpr size_t FF_MULTIPLICATION_COST
Definition thread.hpp:134
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
void fold_stride2(const Polynomial< Fr > &src, Polynomial< Fr > &dst, const size_t begin, const size_t end, const Fr &challenge)
Stride-2 linear-interpolation fold: dst[k] = src[2k] + challenge * (src[2k+1] - src[2k]) for every ou...
void parallel_for_heuristic(size_t num_points, const std::function< void(size_t, size_t, size_t)> &func, size_t heuristic_cost)
Split a loop into several loops running in parallel based on operations in 1 iteration.
Definition thread.cpp:172
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::string to_string(bb::avm2::ValueTag tag)
Curve::ScalarField Fr
auto range(size_t size, size_t offset=0) const
Definition thread.hpp:152
void throw_or_abort(std::string const &err)