Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
sumcheck_round.hpp
Go to the documentation of this file.
1// === AUDIT STATUS ===
2// internal: { status: Complete, auditors: [Khashayar], commit: }
3// external_1: { status: not started, auditors: [], commit: }
4// external_2: { status: not started, auditors: [], commit: }
5// =====================
6
7#pragma once
19#include "zk_sumcheck_data.hpp"
20
21#include <algorithm>
22#include <array>
23#include <bitset>
24#include <optional>
25
26namespace bb {
27
28// Sumcheck SIMD vs scalar path. The prover round evaluates relations in an element type `Element`: scalar
29// `FF` (one trace row per iteration), or `VectorField<FF::Params>` packing `lane_count` trace rows per SIMD
30// lane on WASM SIMD builds of opted-in flavors. The accumulate path is written once against `Element`, with
31// scalar as the width-1 case (`FF::from_lanes` and `FF::horizontal_sum` are identities), so element-type
32// branches survive in only a few named spots. `compute_univariate` resolves `Element` from the flavor's
33// `USE_SIMD_SUMCHECK` opt-in gated by `simd_available_v` -- false on native, so native always runs scalar.
34
35// To know if a flavor is AVM, without including the flavor.
36template <typename Flavor>
37concept isAvmFlavor = std::convertible_to<decltype(Flavor::IS_AVM), bool>;
38
39// True for `VectorField` (it exposes a static `SIZE`), false for scalar `FF`; discriminates the two
40// element types without naming the SIMD type.
41template <typename T>
42concept IsVectorField = requires { T::SIZE; };
43
44// Trace rows an `Element` covers per iteration: 1 for scalar `FF`, `SIZE` for `VectorField`. Sets
45// `accumulate_edge_chunks`'s `EDGE_STRIDE = 2 * lane_count`.
46template <typename T> inline constexpr size_t lane_count = 1;
47template <IsVectorField T> inline constexpr size_t lane_count<T> = T::SIZE;
48
69template <typename Flavor> class SumcheckProverRound {
71
72 public:
73 using FF = typename Flavor::FF;
74 using Relations = typename Flavor::Relations;
75 using SumcheckTupleOfTuplesOfUnivariates = decltype(create_sumcheck_tuple_of_tuples_of_univariates<Relations>());
78 typename Flavor::template ProverUnivariates<2>,
79 typename Flavor::ExtendedEdges>;
80 // See HasLazyShortEdges: native Ultra/Mega materialize edges lazily per column; others extend eagerly.
82 // Flavors whose edge container is materialized on demand (`set_current_edge`) rather than eagerly extended:
83 // AVM (flavor-provided lazy container) and native Ultra/Mega (LazyExtendedEdges wrapper).
85
86 // Edges per work-stealing chunk in the main sumcheck loop. AVM uses smaller (finer-grained) chunks
87 // for better load balance. When the SIMD sumcheck path is active (`SupportsSimdSumcheck<Flavor>` on
88 // a build with `simd_available_v`), 50 rows/chunk = exactly 5 SimdLane batches per chunk
89 // (EDGE_STRIDE = 2 * lane_count<VectorField> = 10), no scalar-tail work. Else, we use 64
90 // rows/chunk.
91 static constexpr size_t ROWS_PER_CHUNK = isAvmFlavor<Flavor> ? 16
92 : (SupportsSimdSumcheck<Flavor> && simd_available_v<typename FF::Params>)
93 ? 50
94 : 64;
96
97 // Number of rows excluded from the main sumcheck loop and handled by compute_offset_area_contribution.
98 // In round 0, the RowDisablingPolynomial disables TRACE_OFFSET rows (2 edge pairs for TRACE_OFFSET=4)
99 // at the TOP of the trace. After partial evaluation in round 1+, this collapses to 2 rows (1 edge pair).
100 // Only non-zero for ZK flavors: non-ZK disabled rows are all zeros and handled by the main loop.
102
107 static constexpr size_t NUM_RELATIONS = Flavor::NUM_RELATIONS;
120 // Note: since this is not initialized with {}, the univariates contain garbage.
122
123 // The length of the polynomials used to mask the Sumcheck Round Univariates.
124 static constexpr size_t LIBRA_UNIVARIATES_LENGTH = Flavor::Curve::LIBRA_UNIVARIATES_LENGTH;
125
126 // Prover constructor
127 SumcheckProverRound(size_t initial_round_size)
128 : round_size(initial_round_size)
129 , multivariate_d(numeric::get_msb(initial_round_size))
130 {
131 BB_BENCH_NAME("SumcheckProverRound constructor");
132
133 // Initialize univariate accumulators to 0
135 }
136
144 {
145 round_size >>= 1;
146 ++round_index;
147 }
148
153 bool is_virtual_round() const { return round_index >= multivariate_d; }
154
171 template <typename ProverPolynomialsOrPartiallyEvaluatedMultivariates>
172 size_t compute_effective_round_size(const ProverPolynomialsOrPartiallyEvaluatedMultivariates& multivariates) const
173 {
174 size_t max_end_index = 0;
175 if constexpr (requires { multivariates.get_witness(); }) {
176 for (auto& witness_poly : multivariates.get_witness()) {
177 max_end_index = std::max(max_end_index, witness_poly.end_index());
178 }
179 } else {
180 return round_size;
181 }
182
183 size_t effective = max_end_index + (max_end_index % 2); // round up to next even
184 // ZK flavors without row disabling (e.g. Translator) must iterate over the full round_size.
186 return round_size;
187 }
188 return std::min(round_size, effective);
189 }
190
218 template <typename ProverPolynomialsOrPartiallyEvaluatedMultivariates>
219 static void extend_edges(ExtendedEdges& extended_edges,
220 const ProverPolynomialsOrPartiallyEvaluatedMultivariates& multivariates,
221 const size_t edge_idx)
222 {
223 for (auto [extended_edge, multivariate] : zip_view(extended_edges.get_all(), multivariates.get_all())) {
224 if constexpr (Flavor::USE_SHORT_MONOMIALS) {
225 extended_edge = bb::Univariate<FF, 2>({ multivariate[edge_idx], multivariate[edge_idx + 1] });
226 } else {
227 // end_index() is exclusive, so end_index() == edge_idx already means the pair
228 // {multivariate[edge_idx], multivariate[edge_idx + 1]} lies entirely in the zero region.
229 if (multivariate.end_index() <= edge_idx) {
230 static const auto zero_univariate = bb::Univariate<FF, MAX_PARTIAL_RELATION_LENGTH>::zero();
231 extended_edge = zero_univariate;
232 } else {
233 extended_edge = bb::Univariate<FF, 2>({ multivariate[edge_idx], multivariate[edge_idx + 1] })
234 .template extend_to<MAX_PARTIAL_RELATION_LENGTH>();
235 }
236 }
237 }
238 }
239
254 template <typename Multivariates, typename Element = FF> class LazyExtendedEdges {
255 public:
256 using EntityId = typename Flavor::template ProverUnivariates<2>::EntityId;
257
258 explicit LazyExtendedEdges(const Multivariates& multivariates)
260 {
261 if constexpr (cache_on_heap) {
263 }
264 }
265
266 void set_current_edge(const size_t edge_idx)
267 {
268 current_edge = edge_idx;
269 materialized.reset();
270 }
271
273 {
274 const size_t index = static_cast<size_t>(id);
275 if (!materialized.test(index)) {
276 const auto& multivariate = multivariates.get_all()[index];
277 // Gather each lane's edge pair: `Element::from_lanes` reads `(current_edge + 2j, +1)` for lane
278 // j -- `lane_count` strided pairs for `VectorField`, just `(current_edge, current_edge + 1)`
279 // for scalar `FF`.
281 { Element::from_lanes([&](size_t lane) { return multivariate[current_edge + (2 * lane)]; }),
282 Element::from_lanes([&](size_t lane) { return multivariate[current_edge + (2 * lane) + 1]; }) });
283 materialized.set(index);
284 }
285 return cache[index];
286 }
287
288 private:
289 const Multivariates& multivariates;
290 // The wide `VectorField` cache (≈ 216 B/entity → ~33 KB for Mega at `NUM_ALL_ENTITIES` ≈ 78) goes on
291 // the heap so it doesn't blow thin WASM worker stacks; the small `FF` cache (~5 KB at 64 B/entity)
292 // stays inline to avoid a per-worker-per-round heap allocation. `materialized` is tens of bytes and
293 // stays inline regardless.
294 static constexpr bool cache_on_heap = IsVectorField<Element>;
298 mutable Cache cache{};
299 size_t current_edge = 0;
300 mutable std::bitset<Flavor::NUM_ALL_ENTITIES> materialized;
301 };
302
303 // Build the per-thread edge container in element type `Element`: lazy per-column for short-monomial
304 // flavors, AVM's eager container, or eager `extend_edges`. `Element` (default `FF`) threads into the lazy
305 // branch; a non-`FF` `Element` reaches only that branch, since SIMD is short-monomial-only (asserted).
306 template <typename Element = FF, typename Multivariates>
307 static auto make_extended_edges(const Multivariates& multivariates)
308 {
310 "SIMD (VectorField) sumcheck is only supported for short-monomial flavors");
311 if constexpr (isAvmFlavor<Flavor>) {
312 return ExtendedEdges(multivariates);
313 } else if constexpr (USE_LAZY_SHORT_EDGES) {
314 return LazyExtendedEdges<Multivariates, Element>(multivariates);
315 } else {
316 return ExtendedEdges{};
317 }
318 }
319
320 // Point an edge container produced by make_extended_edges at edge_idx.
321 template <typename Edges, typename Multivariates>
322 static void load_edge(Edges& edges, const Multivariates& multivariates, const size_t edge_idx)
323 {
324 if constexpr (USE_LAZY_EDGES) {
325 edges.set_current_edge(edge_idx);
326 } else {
327 extend_edges(edges, multivariates, edge_idx);
328 }
329 }
330
331 // The element type surfaces below only in `element_scaling`, `RelationTupleFor` / `AccumulatorsFor`, and
332 // `reduce_accumulator` (see the SIMD/scalar note at the top of the file).
333
334 // Per-iteration gate-separator factor as an `Element`, via the stride-2 `GateSeparatorPolynomial::gather`
335 // (width-1 `gate_separators[edge_idx]` for `FF`). MultilinearBatching has no `pow_beta`, so the factor is 1.
336 template <typename Element>
337 static Element element_scaling(const bb::GateSeparatorPolynomial<FF>& gate_separators, const size_t edge_idx)
338 {
340 return Element{ 1 };
341 }
342 return gate_separators.template gather<Element>(edge_idx);
343 }
344
345 // Relation set instantiated over `Element`: the canonical `Relations` for `FF`, or
346 // `Flavor::Relations_<Element>` re-instantiated with the SIMD element.
347 template <typename Element> using RelationTupleFor = typename Flavor::template Relations_<Element>;
348
349 // Per-relation / per-subrelation accumulator over `Element`: a tuple (one entry per relation) of tuples
350 // (one `Univariate<Element, LENGTH>` per subrelation), so the coefficient type is `Element`. For `FF` this
351 // is exactly `SumcheckTupleOfTuplesOfUnivariates`.
352 template <typename Element>
353 using AccumulatorsFor = decltype(create_sumcheck_tuple_of_tuples_of_univariates<RelationTupleFor<Element>>());
354
355 // Horizontally reduce a per-slot `Element` accumulator into the FF round accumulator: for each coefficient,
356 // add its `horizontal_sum()` (per-lane sum for `VectorField`) into the matching FF coefficient. `source`
357 // and `destination` share shape but differ in coefficient type (`Element` vs `FF`), so
358 // `Utils::add_nested_tuples` (same-type only) can't be used.
359 template <typename Element>
361 const AccumulatorsFor<Element>& source)
362 {
363 constexpr_for<0, NUM_RELATIONS, 1>([&]<size_t relation_idx>() {
364 auto& destination_relation = std::get<relation_idx>(destination);
365 const auto& source_relation = std::get<relation_idx>(source);
366 constexpr_for<0, std::tuple_size_v<std::decay_t<decltype(source_relation)>>, 1>(
367 [&]<size_t subrelation_idx>() {
368 auto& destination_univariate = std::get<subrelation_idx>(destination_relation);
369 const auto& source_univariate = std::get<subrelation_idx>(source_relation);
370 for (size_t k = 0; k < std::decay_t<decltype(source_univariate)>::LENGTH; ++k) {
371 // Per-lane sum for `VectorField`; identity (plain add) for `FF`.
372 destination_univariate.evaluations[k] += source_univariate.evaluations[k].horizontal_sum();
373 }
374 });
375 });
376 }
377
392 template <typename Element = void, typename ProverPolynomialsOrPartiallyEvaluatedMultivariates>
393 SumcheckRoundUnivariate compute_univariate(ProverPolynomialsOrPartiallyEvaluatedMultivariates& polynomials,
394 const bb::RelationParameters<FF>& relation_parameters,
395 const bb::GateSeparatorPolynomial<FF>& gate_separators,
396 const SubrelationSeparators& alphas)
397 {
398 BB_BENCH_NAME("compute_univariate");
399
400 // `Element` defaults to the `void` sentinel, resolved here to `VectorField<FF::Params>` for opted-in
401 // flavors (`SupportsSimdSumcheck`) when SIMD is available (`simd_available_v`) and rows aren't skipped,
402 // else `FF`. The `VectorField` arm is named but not instantiated when unused, so non-opted-in flavors
403 // compile. `-DBB_FORCE_SCALAR_LANE=1` forces `FF` (A/B benchmarks); parity tests pass `FF` /
404 // `VectorField<FF::Params>` explicitly.
405#ifdef BB_FORCE_SCALAR_LANE
406 using AutoResolved = FF;
407#else
408 using AutoResolved =
409 std::conditional_t<SupportsSimdSumcheck<Flavor> && simd_available_v<typename FF::Params> &&
410 !USES_ROW_MANIFEST<ProverPolynomialsOrPartiallyEvaluatedMultivariates>,
412 FF>;
413#endif
414 using ResolvedElement = std::conditional_t<std::is_same_v<Element, void>, AutoResolved, Element>;
416 "SIMD element requested for a flavor that does not support the row-parallel path");
417
418 auto chunks = make_edge_chunks(polynomials);
419 accumulate_edge_chunks<ResolvedElement>(chunks, polynomials, relation_parameters, gate_separators);
420 return batch_over_relations<SumcheckRoundUnivariate>(univariate_accumulators, alphas, gate_separators);
421 }
422
423 struct EdgeRange {
424 size_t begin;
425 size_t end;
426 };
427
428 // Number of fixed-size chunks needed to cover `span` edges.
429 static size_t chunk_count(const size_t span, const size_t rows_per_chunk)
430 {
431 return span / rows_per_chunk + (span % rows_per_chunk > 0 ? 1 : 0);
432 }
433
434 // Work-stealing scheduler over a single contiguous edge range (the dense-flavor case, see
435 // `compute_univariate`). `pop()` computes chunk bounds arithmetically, so it allocates nothing.
437 const size_t begin;
438 const size_t end;
439 const size_t rows_per_chunk;
440 const size_t total_chunks;
441 std::atomic<size_t> next_chunk{ 0 };
442
443 ContiguousEdgeChunks(const size_t begin, const size_t end, const size_t rows_per_chunk)
444 : begin(begin)
445 , end(end)
448 {
449 BB_ASSERT(begin % 2 == 0, "edge range begin must be even");
450 BB_ASSERT(end % 2 == 0, "edge range end must be even");
451 BB_ASSERT(begin <= end, "edge range begin must not exceed end");
452 BB_ASSERT(rows_per_chunk >= 2 && rows_per_chunk % 2 == 0, "rows_per_chunk must be at least 2 and even");
453 }
454
455 size_t num_slots() const { return std::min(bb::get_num_cpus(), std::max<size_t>(total_chunks, 1)); }
456
458 {
459 const size_t id = next_chunk.fetch_add(1, std::memory_order_relaxed);
460 if (id >= total_chunks) {
461 return std::nullopt;
462 }
463 const size_t chunk_begin = begin + id * rows_per_chunk;
464 return EdgeRange{ .begin = chunk_begin, .end = std::min(chunk_begin + rows_per_chunk, end) };
465 }
466 };
467
468 // Work-stealing scheduler over a manifest of contiguous ranges (the row-skipping case, see
469 // `compute_univariate`). The ranges are flattened into a chunk list up front, since a single arithmetic
470 // stride can't express the gaps between them; the manifest is small, so the materialization cost is bounded.
473 std::atomic<size_t> next_chunk{ 0 };
474
475 ListedEdgeChunks(const std::vector<EdgeRange>& ranges, const size_t rows_per_chunk)
476 {
477 BB_ASSERT(rows_per_chunk >= 2 && rows_per_chunk % 2 == 0, "rows_per_chunk must be at least 2 and even");
478
479 size_t num_chunks = 0;
480 for (const EdgeRange& range : ranges) {
481 BB_ASSERT(range.begin % 2 == 0, "edge range begin must be even");
482 BB_ASSERT(range.end % 2 == 0, "edge range end must be even");
483 BB_ASSERT(range.begin <= range.end, "edge range begin must not exceed end");
484 num_chunks += chunk_count(range.end - range.begin, rows_per_chunk);
485 }
486
487 chunks.reserve(num_chunks);
488 for (const EdgeRange& range : ranges) {
489 for (size_t chunk_begin = range.begin; chunk_begin < range.end; chunk_begin += rows_per_chunk) {
490 chunks.push_back(
491 EdgeRange{ .begin = chunk_begin, .end = std::min(chunk_begin + rows_per_chunk, range.end) });
492 }
493 }
494 }
495
496 size_t num_slots() const { return std::min(bb::get_num_cpus(), std::max<size_t>(chunks.size(), 1)); }
497
499 {
500 const size_t id = next_chunk.fetch_add(1, std::memory_order_relaxed);
501 if (id >= chunks.size()) {
502 return std::nullopt;
503 }
504 return chunks[id];
505 }
506 };
507
508 // Build the work-stealing scheduler for the round's active edges (see `compute_univariate` for the taxonomy):
509 // a `ListedEdgeChunks` manifest when the flavor skips rows, else a single-range `ContiguousEdgeChunks`. The
510 // scheduler type is selected at compile time.
511 template <typename ProverPolynomialsOrPartiallyEvaluatedMultivariates>
512 auto make_edge_chunks(ProverPolynomialsOrPartiallyEvaluatedMultivariates& polynomials)
513 {
514 if constexpr (USES_ROW_MANIFEST<ProverPolynomialsOrPartiallyEvaluatedMultivariates>) {
515 std::vector<EdgeRange> round_manifest;
516 {
517 BB_BENCH_NAME("compute_univariate/compute_manifest");
518 round_manifest = compute_edge_ranges(polynomials);
519 }
520 return ListedEdgeChunks{ round_manifest, ROWS_PER_CHUNK };
521 } else {
522 // Short traces don't need to iterate over the zero tail of the polynomial.
523 const size_t effective_round_size = compute_effective_round_size(polynomials);
524 return ContiguousEdgeChunks{ excluded_head_size, effective_round_size, ROWS_PER_CHUNK };
525 }
526 }
527
528 // The two-phase edge sweep behind `accumulate_edge_chunks`. Walks the edge-pairs of one chunk
529 // `[begin, end)` in two passes: `on_full_group(idx)` for each `group_stride`-point group -- where the
530 // accumulate loop gathers one SIMD batch of `lane_count` edge-pairs into the wide `Element` accumulator --
531 // then `on_leftover_pair(idx)` for each trailing edge-pair (two points) that didn't fill a group -- the
532 // scalar tail into the FF result. For the scalar lane `group_stride == 2`: every edge-pair is its own
533 // group, so the first pass covers everything and there is no tail.
534 template <typename GroupFn, typename PairFn>
535 [[gnu::always_inline]] static void for_each_edge_group(
536 const size_t begin, const size_t end, const size_t group_stride, GroupFn on_full_group, PairFn on_leftover_pair)
537 {
538 size_t edge_idx = begin;
539 for (; edge_idx + group_stride <= end; edge_idx += group_stride) {
540 on_full_group(edge_idx);
541 }
542 for (; edge_idx < end; edge_idx += 2) {
543 on_leftover_pair(edge_idx);
544 }
545 }
546
547 // Fold one edge's relation contributions into `accumulator`: position `edge_container` at `edge_idx`,
548 // then run the relation set (in the element type `Element`, deduced from `params`) scaled by the
549 // gate-separator factor. Both `accumulate_edge_chunks` passes share this body -- the full-group pass
550 // calls it with the wide `VectorField` element (`edges` / `wide_accumulator`), the leftover-pair pass
551 // with the scalar `FF` element (`tail_edges` / `result`); they differ only in which element they pass.
552 template <typename Accumulators, typename Edges, typename Multivariates, typename Element>
553 void accumulate_edge(Accumulators& accumulator,
554 Edges& edge_container,
555 const Multivariates& polynomials,
557 const bb::GateSeparatorPolynomial<FF>& gate_separators,
558 const size_t edge_idx)
559 {
560 SumcheckProverRound::load_edge(edge_container, polynomials, edge_idx);
561 accumulate_relation_univariates<RelationTupleFor<Element>>(
562 accumulator, edge_container, params, element_scaling<Element>(gate_separators, edge_idx));
563 }
564
565 // Accumulate the round univariate over `chunks` in element type `Element`. Each work-stealing slot owns a
566 // heap-backed `Element` accumulator (WASM worker stacks are small) and processes `EDGE_STRIDE =
567 // 2 * lane_count<Element>` edges per iteration; the sub-stride remainder is mopped up by a scalar tail
568 // into an FF result, into which the wide accumulator is horizontally reduced at slot end. Slots are
569 // disjoint, so the parallel section needs no synchronization; per-slot results are summed afterwards.
570 template <typename Element, typename EdgeChunks, typename ProverPolynomialsOrPartiallyEvaluatedMultivariates>
571 void accumulate_edge_chunks(EdgeChunks& chunks,
572 ProverPolynomialsOrPartiallyEvaluatedMultivariates& polynomials,
573 const bb::RelationParameters<FF>& relation_parameters,
574 const bb::GateSeparatorPolynomial<FF>& gate_separators)
575 {
576 constexpr size_t EDGE_STRIDE = 2 * lane_count<Element>;
577 const size_t num_slots = chunks.num_slots();
578 const auto element_parameters = relation_parameters.template convert_to<Element>();
579
580 std::vector<AccumulatorsFor<Element>> slot_wide_accumulators(num_slots);
582
583 parallel_for(num_slots, [&](size_t slot_id) {
584 auto edges = make_extended_edges<Element>(polynomials);
585 auto tail_edges = make_extended_edges(polynomials);
586 auto& wide_accumulator = slot_wide_accumulators[slot_id];
587 auto& result = slot_results[slot_id];
588 while (auto chunk = chunks.pop()) {
589 // Both passes call the same `accumulate_edge`, differing only in the element folded in: the
590 // full-group pass a wide `Element` batch (`edges`) into `wide_accumulator`, the leftover pass
591 // each single scalar edge (`tail_edges`) into `result`.
593 chunk->begin,
594 chunk->end,
595 EDGE_STRIDE,
596 [&](const size_t edge_idx) {
597 accumulate_edge(
598 wide_accumulator, edges, polynomials, element_parameters, gate_separators, edge_idx);
599 },
600 [&](const size_t edge_idx) {
601 accumulate_edge(
602 result, tail_edges, polynomials, relation_parameters, gate_separators, edge_idx);
603 });
604 }
605 reduce_accumulator<Element>(result, wide_accumulator);
606 });
607
608 for (auto& result : slot_results) {
610 }
611 }
612
613 // True when the flavor exposes a static row-skip manifest: a contiguous prefix [head, active_prefix_end) holding
614 // every relation-active row, used directly instead of the row-by-row skip_entire_row scan below. Only sound when
615 // the prefix is tight (no inactive rows inside it); flavors whose active rows are interspersed should omit it and
616 // use the dynamic scan. See Flavor::row_skip_active_prefix_end.
617 template <typename ProverPolynomialsOrPartiallyEvaluatedMultivariates>
618 static constexpr bool HAS_STATIC_ROW_SKIP_MANIFEST =
620 requires(const ProverPolynomialsOrPartiallyEvaluatedMultivariates& polynomials) {
622 };
623
624 // True when the flavor exposes a per-row `skip_entire_row` predicate, used to dynamically scan the trace for
625 // contiguous runs of relation-active rows.
626 template <typename ProverPolynomialsOrPartiallyEvaluatedMultivariates>
627 static constexpr bool CAN_SKIP_ROWS =
629
630 // True when the round univariate is computed over a manifest of relation-active edge ranges (the row-skipping
631 // case in `compute_univariate`) rather than the whole contiguous active range -- i.e. either row-skip predicate.
632 template <typename ProverPolynomialsOrPartiallyEvaluatedMultivariates>
633 static constexpr bool USES_ROW_MANIFEST =
634 HAS_STATIC_ROW_SKIP_MANIFEST<ProverPolynomialsOrPartiallyEvaluatedMultivariates> ||
635 CAN_SKIP_ROWS<ProverPolynomialsOrPartiallyEvaluatedMultivariates>;
636
637 static size_t round_up_to_even(const size_t value) { return value + (value & 1U); }
638
639 static void append_edge_range(std::vector<EdgeRange>& ranges, const size_t start, const size_t end)
640 {
641 if (end <= start) {
642 return;
643 }
644 if (!ranges.empty()) {
645 auto& previous = ranges.back();
646 const size_t previous_end = previous.end;
647 if (start <= previous_end) {
648 previous.end = std::max(previous_end, end);
649 return;
650 }
651 }
652 ranges.push_back(EdgeRange{ .begin = start, .end = end });
653 }
654
656 {
657 if (ranges.empty()) {
658 return;
659 }
660 std::sort(ranges.begin(), ranges.end(), [](const EdgeRange& lhs, const EdgeRange& rhs) {
661 return lhs.begin < rhs.begin;
662 });
663
664 size_t write_idx = 0;
665 for (size_t read_idx = 1; read_idx < ranges.size(); ++read_idx) {
666 auto& previous = ranges[write_idx];
667 const auto& current = ranges[read_idx];
668 const size_t previous_end = previous.end;
669 if (current.begin <= previous_end) {
670 previous.end = std::max(previous_end, current.end);
671 } else {
672 ++write_idx;
673 ranges[write_idx] = current;
674 }
675 }
676 ranges.resize(write_idx + 1);
677 }
678
679 template <typename ProverPolynomialsOrPartiallyEvaluatedMultivariates>
680 std::vector<EdgeRange> compute_row_skip_edge_ranges(ProverPolynomialsOrPartiallyEvaluatedMultivariates& polynomials,
681 const size_t effective_round_size) const
682 {
683 const size_t scan_start = excluded_head_size;
685 if (effective_round_size <= scan_start) {
686 return ranges;
687 }
688
689 if constexpr (HAS_STATIC_ROW_SKIP_MANIFEST<ProverPolynomialsOrPartiallyEvaluatedMultivariates>) {
690 const size_t row_skip_active_prefix_end = Flavor::row_skip_active_prefix_end(polynomials);
691 if (row_skip_active_prefix_end == 0) {
692 append_edge_range(ranges, scan_start, effective_round_size);
693 return ranges;
694 }
695
696 const size_t active_prefix_end =
697 std::min(round_up_to_even(row_skip_active_prefix_end), effective_round_size);
698 append_edge_range(ranges, scan_start, std::max(scan_start, active_prefix_end));
699
700 // Lagrange-last lives at the end of the domain. Everything between the active prefix and this final
701 // edge-pair is known to be relation-trivial, so do not spend scan work proving it row-by-row.
702 if (effective_round_size >= scan_start + 2) {
703 append_edge_range(ranges, effective_round_size - 2, effective_round_size);
704 }
705 } else {
706 append_edge_range(ranges, scan_start, effective_round_size);
707 }
708 return ranges;
709 }
710
721 template <typename ProverPolynomialsOrPartiallyEvaluatedMultivariates>
722 std::vector<EdgeRange> compute_edge_ranges(ProverPolynomialsOrPartiallyEvaluatedMultivariates& polynomials)
723 {
724 // When !HasZK, compute the effective round size to avoid iterating over zero regions
725 const size_t effective_round_size = compute_effective_round_size(polynomials);
726
728 if constexpr (HAS_STATIC_ROW_SKIP_MANIFEST<ProverPolynomialsOrPartiallyEvaluatedMultivariates>) {
729 // Static row manifests describe the relation-active edge pairs directly, avoiding the old per-row skip
730 // scan over the whole trace.
731 result = compute_row_skip_edge_ranges(polynomials, effective_round_size);
732 } else if constexpr (CAN_SKIP_ROWS<ProverPolynomialsOrPartiallyEvaluatedMultivariates>) {
733 // Iterate over edge-pairs (stride-2) so each thread gets an even-aligned range.
734 const std::vector<EdgeRange> scan_ranges = compute_row_skip_edge_ranges(polynomials, effective_round_size);
735 // Cost per iteration: skip_entire_row reads across polynomial columns.
736 // Overestimates by using total entity count (skip_entire_row only checks a subset).
737 constexpr size_t heuristic_cost = bb::thread_heuristics::FF_COPY_COST * 2 * Flavor::NUM_ALL_ENTITIES;
739
740 for (const auto& scan_range : scan_ranges) {
741 const size_t num_edge_pairs = (scan_range.end - scan_range.begin) / 2;
743 num_edge_pairs,
744 [&](ThreadChunk chunk) {
745 auto range = chunk.range(num_edge_pairs);
746 if (range.empty()) {
747 return;
748 }
749 // Scan edge pairs to find contiguous live ranges.
750 size_t current_block_start = 0;
751 size_t current_block_size = 0;
752 std::vector<EdgeRange> thread_ranges;
753 for (size_t pair_idx : range) {
754 size_t edge_idx = scan_range.begin + pair_idx * 2;
755 if (!Flavor::skip_entire_row(polynomials, edge_idx)) {
756 if (current_block_size == 0) {
757 current_block_start = edge_idx;
758 }
759 current_block_size += 2; // each pair covers 2 edges
760 } else {
761 if (current_block_size > 0) {
762 thread_ranges.push_back(
763 EdgeRange{ .begin = current_block_start,
764 .end = current_block_start + current_block_size });
765 current_block_size = 0;
766 }
767 }
768 }
769 if (current_block_size > 0) {
770 thread_ranges.push_back(EdgeRange{ .begin = current_block_start,
771 .end = current_block_start + current_block_size });
772 }
773 auto& ranges = all_thread_ranges[chunk.thread_index];
774 ranges.insert(ranges.end(), thread_ranges.begin(), thread_ranges.end());
775 },
776 heuristic_cost);
777 }
778
779 for (const auto& thread_ranges : all_thread_ranges) {
780 result.insert(result.end(), thread_ranges.begin(), thread_ranges.end());
781 }
783 } else {
784 // The disabled head rows are handled by compute_offset_area_contribution, so skip them here.
785 result.push_back(EdgeRange{ .begin = excluded_head_size, .end = effective_round_size });
786 }
787 return result;
788 }
789
810 template <typename ProverPolynomialsOrPartiallyEvaluatedMultivariates>
812 ProverPolynomialsOrPartiallyEvaluatedMultivariates& polynomials,
813 const bb::RelationParameters<FF>& relation_parameters,
814 const bb::GateSeparatorPolynomial<FF>& gate_separators,
815 const SubrelationSeparators& alphas,
816 const RowDisablingPolynomial<FF> row_disabling_polynomial)
818 {
819 SumcheckTupleOfTuplesOfUnivariates univariate_accumulator{};
820 auto extended_edges = make_extended_edges(polynomials);
821
822 for (size_t edge_idx = 0; edge_idx < excluded_head_size; edge_idx += 2) {
823 load_edge(extended_edges, polynomials, edge_idx);
825 univariate_accumulator, extended_edges, relation_parameters, gate_separators[edge_idx]);
826 }
827
828 return batch_over_relations<SumcheckRoundUnivariate>(
829 univariate_accumulator, alphas, gate_separators, &row_disabling_polynomial);
830 }
831
835 template <typename ProverPolynomialsOrPartiallyEvaluatedMultivariates>
837 ProverPolynomialsOrPartiallyEvaluatedMultivariates& polynomials,
838 const bb::RelationParameters<FF>& relation_parameters,
839 const GateSeparatorPolynomial<FF>& gate_separator,
840 const SubrelationSeparators& alphas,
841 const RowDisablingPolynomial<FF>* row_disabling_polynomial = nullptr)
842 {
843 // A virtual (zero-extension) contribution is only well-defined once all multivariate_d regular rounds have
844 // run: it treats the prover polynomials as padded by zero beyond the real hypercube.
846 "compute_virtual_contribution must only run in virtual rounds (after all regular rounds)");
847
848 // Note: {} is required to initialize the tuple contents. Otherwise the univariates contain garbage.
849 SumcheckTupleOfTuplesOfUnivariates univariate_accumulator{};
850
851 // For a given prover polynomial P_i(X_0, ..., X_{d-1}) extended by zero, i.e. multiplied by
852 // \tau(X_d, ..., X_{virtual_log_n - 1}) = \prod (1 - X_k)
853 // for k = d, ..., virtual_log_n - 1, the computation of the virtual sumcheck round univariate reduces to the
854 // edge (0, ...,0).
855 const size_t virtual_contribution_edge_idx = 0;
856
857 // Perform the usual sumcheck accumulation, but for a single edge.
858 auto extended_edges = make_extended_edges(polynomials);
859 load_edge(extended_edges, polynomials, virtual_contribution_edge_idx);
860
861 // The tail of G(X) = \prod_{k} (1 + X_k(\beta_k - 1) ) evaluated at the edge (0, ..., 0).
862 const FF gate_separator_tail{ 1 };
864 univariate_accumulator, extended_edges, relation_parameters, gate_separator_tail);
865
866 return batch_over_relations<SumcheckRoundUnivariate>(
867 univariate_accumulator, alphas, gate_separator, row_disabling_polynomial);
868 }
869
886 template <typename ExtendedUnivariate, typename ContainerOverSubrelations>
887 static ExtendedUnivariate batch_over_relations(ContainerOverSubrelations& univariate_accumulators,
888 const SubrelationSeparators& challenge,
889 const bb::GateSeparatorPolynomial<FF>& gate_separators,
890 const RowDisablingPolynomial<FF>* row_disabling_polynomial = nullptr)
891 {
893
894 auto result = ExtendedUnivariate(0);
895 extend_and_batch_univariates<ExtendedUnivariate>(
896 univariate_accumulators, result, gate_separators, row_disabling_polynomial);
897
898 // Reset all univariate accumulators to 0 before beginning accumulation in the next round
900 return result;
901 }
902
923 template <typename ExtendedUnivariate, typename TupleOfTuplesOfUnivariates>
924 static void extend_and_batch_univariates(const TupleOfTuplesOfUnivariates& tuple,
925 ExtendedUnivariate& result,
926 const bb::GateSeparatorPolynomial<FF>& gate_separators,
927 const RowDisablingPolynomial<FF>* row_disabling_polynomial = nullptr)
928 {
929 // Pow-Factor \f$ (1-X) + X\beta_i \f$
930 auto random_polynomial = bb::Univariate<FF, 2>({ 1, gate_separators.current_element() });
931 ExtendedUnivariate extended_random_polynomial =
932 random_polynomial.template extend_to<ExtendedUnivariate::LENGTH>();
933
934 // Row-disabling factors. Defaults (1, 0) encode "no row disabling": main relations pass
935 // through unscaled and offset-only relations collapse to zero. When a row-disabling
936 // polynomial is supplied, `L^{(i)}(X) = L(u_0, ..., u_{i-1}, X, 0, ..., 0)` is a linear
937 // univariate with evals `eval_at_0/1`; the main-domain factor is `(1 - L^{(i)})(X)` and
938 // the offset-only factor is `L^{(i)}(X)`.
939 bb::Univariate<FF, 2> main_linear({ FF::one(), FF::one() });
940 bb::Univariate<FF, 2> offset_linear({ FF::zero(), FF::zero() });
941 if (row_disabling_polynomial != nullptr) {
942 main_linear = bb::Univariate<FF, 2>(
943 { FF::one() - row_disabling_polynomial->eval_at_0, FF::one() - row_disabling_polynomial->eval_at_1 });
944 offset_linear =
945 bb::Univariate<FF, 2>({ row_disabling_polynomial->eval_at_0, row_disabling_polynomial->eval_at_1 });
946 }
947 const ExtendedUnivariate main_factor = main_linear.template extend_to<ExtendedUnivariate::LENGTH>();
948 const ExtendedUnivariate offset_factor = offset_linear.template extend_to<ExtendedUnivariate::LENGTH>();
949
950 // Extend and batch one relation's subrelation accumulators, applying the appropriate
951 // row-disabling factor. Independent across relations, so it can run serially or in parallel.
952 auto batch_one_relation = [&]<size_t relation_idx>() -> ExtendedUnivariate {
954 const auto& outer_element = std::get<relation_idx>(tuple);
955
956 ExtendedUnivariate per_relation(0);
957 constexpr_for<0, std::tuple_size_v<std::decay_t<decltype(outer_element)>>, 1>(
958 [&]<size_t subrelation_idx>() {
959 const auto& element = std::get<subrelation_idx>(outer_element);
960 auto extended = element.template extend_to<ExtendedUnivariate::LENGTH>();
961
962 constexpr bool is_subrelation_linearly_independent =
963 bb::subrelation_is_linearly_independent<Relation, subrelation_idx>();
964 // Except for the log-derivative subrelation, each subrelation is required to
965 // vanish at every point of the hypercube, hence we multiply by the pow
966 // polynomial. Since the sumcheck prover sends a univariate to the verifier, we
967 // additionally apply the univariate contribution `extended_random_polynomial`.
968 if constexpr (!is_subrelation_linearly_independent) {
969 per_relation += extended;
970 } else {
971 // Multiply by the pow polynomial univariate contribution and the partial
972 // evaluation \f$ c_i = pow_\beta(u_0, ..., u_{i-1}) \f$.
973 per_relation +=
974 extended * extended_random_polynomial * gate_separators.partial_evaluation_result;
975 }
976 });
977
978 if constexpr (IsOffsetOnlyRelation<Relation>) {
979 return per_relation * offset_factor;
980 } else {
981 return per_relation * main_factor;
982 }
983 };
984
985 constexpr size_t num_relations_in_tuple = std::tuple_size_v<TupleOfTuplesOfUnivariates>;
986 // Batching runs every round at a fixed cost independent of the round size, so for flavors with many
987 // high-degree subrelations (ECCVM) it becomes a serial per-round floor dominating the geometrically
988 // shrinking sumcheck tail. Such flavors opt into parallel batching (ParallelizesRelationBatching); other
989 // flavors batch serially, where thread dispatch would cost more than it saves.
991 // One relation per slot; sum in relation order afterwards so the result is schedule-independent.
993 parallel_for(num_relations_in_tuple, [&](size_t slot) {
994 constexpr_for<0, num_relations_in_tuple, 1>([&]<size_t relation_idx>() {
995 if (relation_idx == slot) {
996 per_relation_results[relation_idx] = batch_one_relation.template operator()<relation_idx>();
997 }
998 });
999 });
1000 for (const auto& per_relation : per_relation_results) {
1001 result += per_relation;
1002 }
1003 } else {
1004 constexpr_for<0, num_relations_in_tuple, 1>(
1005 [&]<size_t relation_idx>() { result += batch_one_relation.template operator()<relation_idx>(); });
1006 }
1007 }
1008
1021 static SumcheckRoundUnivariate compute_libra_univariate(const ZKData& zk_sumcheck_data, size_t round_idx)
1022 {
1023 BB_ASSERT(round_idx < zk_sumcheck_data.libra_univariates.size(),
1024 "compute_libra_univariate: round_idx out of range");
1025 bb::Univariate<FF, LIBRA_UNIVARIATES_LENGTH> libra_round_univariate;
1026 // select the i'th column of Libra book-keeping table
1027 const auto& current_column = zk_sumcheck_data.libra_univariates[round_idx];
1028 // the evaluation of Libra round univariate at k=0...D are equal to \f$\texttt{libra_univariates}_{i}(k)\f$
1029 // corrected by the Libra running sum
1030 for (size_t idx = 0; idx < LIBRA_UNIVARIATES_LENGTH; ++idx) {
1031 libra_round_univariate.value_at(idx) =
1032 current_column.evaluate(FF(idx)) + zk_sumcheck_data.libra_running_sum;
1033 };
1035 return libra_round_univariate;
1036 } else {
1037 return libra_round_univariate.template extend_to<SumcheckRoundUnivariate::LENGTH>();
1038 }
1039 }
1040
1041 // Methods made accessible for testing
1043 const auto& extended_edges,
1044 const bb::RelationParameters<FF>& relation_parameters,
1045 const FF& scaling_factor)
1046 {
1047 accumulate_relation_univariates(univariate_accumulators, extended_edges, relation_parameters, scaling_factor);
1048 }
1049
1050 private:
1078 template <typename RelationTuple = Relations, typename Accumulators, typename Edges, typename Element>
1080 const Edges& extended_edges,
1081 const bb::RelationParameters<Element>& relation_parameters,
1082 const Element& scaling_factor)
1083 {
1084 // `RelationTuple` is `Relations` or `Flavor::Relations_<Element>` -- the same relation set over
1085 // different fields, so its size is always NUM_RELATIONS.
1087 constexpr_for<0, NUM_RELATIONS, 1>([&]<size_t relation_idx>() {
1089 // Check if the relation is skippable to speed up accumulation
1090 if constexpr (!isSkippable<Relation, decltype(extended_edges)>) {
1091 // If not, accumulate normally
1092 Relation::accumulate(std::get<relation_idx>(univariate_accumulators),
1093 extended_edges,
1094 relation_parameters,
1095 scaling_factor);
1096 } else {
1097 // If so, only compute the contribution if the relation is active
1098 if (!Relation::skip(extended_edges)) {
1099 Relation::accumulate(std::get<relation_idx>(univariate_accumulators),
1100 extended_edges,
1101 relation_parameters,
1102 scaling_factor);
1103 }
1104 }
1105 });
1106 }
1107
1113 // Number of regular sumcheck rounds, i.e. log2 of the initial hypercube size passed to the constructor.
1115 // Incremented once per regular round via advance_round(); reaches multivariate_d after all regular rounds.
1116 size_t round_index = 0;
1117};
1118
1131template <typename Flavor, bool CommittedSumcheck = UsesCommittedSumcheck<Flavor>> class SumcheckVerifierRound {
1132 using FF = typename Flavor::FF;
1135 using TupleOfArraysOfValues = decltype(create_tuple_of_arrays_of_values<typename Flavor::Relations>());
1137
1138 public:
1140 using ClaimedLibraEvaluations = typename std::vector<FF>;
1143
1144 bool round_failed = false;
1145 static constexpr size_t NUM_RELATIONS = Flavor::NUM_RELATIONS;
1148
1151
1157
1162 {
1163 // OriginTag false positive: The univariate is constrained by the sumcheck relation S^i(0) + S^i(1) =
1164 // S^{i-1}(u_{i-1}).
1165 if constexpr (IsRecursiveFlavor<Flavor>) {
1166 const auto bound_tag = target_total_sum.get_origin_tag();
1167 for (auto& eval : univariate.evaluations) {
1168 eval.set_origin_tag(bound_tag);
1169 }
1170 }
1171
1172 FF total_sum = univariate.value_at(0) + univariate.value_at(1);
1173 bool sumcheck_round_failed(false);
1174 if constexpr (IsRecursiveFlavor<Flavor>) {
1175 sumcheck_round_failed = (target_total_sum.get_value() != total_sum.get_value());
1176 target_total_sum.assert_equal(total_sum);
1177 } else {
1178 sumcheck_round_failed = (target_total_sum != total_sum);
1179 }
1180 round_failed = round_failed || sumcheck_round_failed;
1181 };
1182
1187 {
1188 target_total_sum = univariate.evaluate(round_challenge);
1189 }
1190
1199 const bb::RelationParameters<FF>& relation_parameters,
1200 const bb::GateSeparatorPolynomial<FF>& gate_separators,
1201 const SubrelationSeparators& alphas,
1202 std::span<const FF> multivariate_challenge = {})
1203 {
1204 Utils::template accumulate_relation_evaluations_without_skipping<>(purported_evaluations,
1206 relation_parameters,
1207 gate_separators.partial_evaluation_result);
1208 FF main_factor{ 1 };
1209 FF offset_factor{ 0 };
1210 if constexpr (UseRowDisablingPolynomial<Flavor> && Flavor::HasZK) {
1211 main_factor = RowDisablingPolynomial<FF>::evaluate_at_challenge(multivariate_challenge,
1212 multivariate_challenge.size());
1213 offset_factor = FF{ 1 } - main_factor;
1214 }
1215 return Utils::scale_and_batch_elements(relation_evaluations, alphas, main_factor, offset_factor);
1216 }
1217
1224 void process_round(const std::shared_ptr<Transcript>& transcript,
1225 std::vector<FF>& multivariate_challenge,
1226 bb::GateSeparatorPolynomial<FF>& gate_separators,
1227 size_t round_idx)
1228 {
1229 // Obtain the round univariate from the transcript
1230 std::string round_univariate_label = "Sumcheck:univariate_" + std::to_string(round_idx);
1231 auto round_univariate =
1232 transcript->template receive_from_prover<bb::Univariate<FF, BATCHED_RELATION_PARTIAL_LENGTH>>(
1233 round_univariate_label);
1234 FF round_challenge = transcript->template get_challenge<FF>("Sumcheck:u_" + std::to_string(round_idx));
1235 multivariate_challenge.emplace_back(round_challenge);
1236 // Check that $\tilde{S}^{i-1}(u_{i-1}) == \tilde{S}^{i}(0) + \tilde{S}^{i}(1)$
1237 // For i = 0, check that $\tilde{S}^0(u_0) == target_total_sum$
1238 check_sum(round_univariate);
1239 // Evaluate $\tilde{S}^{i}(u_i)$
1240 compute_next_target_sum(round_univariate, round_challenge);
1241 gate_separators.partially_evaluate(round_challenge);
1242 }
1243
1248 bool perform_final_verification(const FF& full_honk_purported_value)
1249 {
1250 bool verified = false;
1251 if constexpr (IsRecursiveFlavor<Flavor>) {
1252 verified = (full_honk_purported_value.get_value() == target_total_sum.get_value());
1253 full_honk_purported_value.assert_equal(target_total_sum);
1254 } else {
1255 verified = (full_honk_purported_value == target_total_sum);
1256 }
1257 return verified;
1258 }
1259
1263 std::vector<Commitment> get_round_univariate_commitments() { return {}; }
1264
1269};
1270
1275template <typename Flavor> class SumcheckVerifierRound<Flavor, true> {
1276 using FF = typename Flavor::FF;
1279 using TupleOfArraysOfValues = decltype(create_tuple_of_arrays_of_values<typename Flavor::Relations>());
1281
1282 public:
1284 using ClaimedLibraEvaluations = typename std::vector<FF>;
1287
1288 bool round_failed = false;
1289 static constexpr size_t NUM_RELATIONS = Flavor::NUM_RELATIONS;
1292
1295
1296 // Grumpkin-specific state for Shplemini
1297 std::vector<Commitment> round_univariate_commitments;
1299
1305
1311 const bb::RelationParameters<FF>& relation_parameters,
1312 const bb::GateSeparatorPolynomial<FF>& gate_separators,
1313 const SubrelationSeparators& alphas,
1314 std::span<const FF> multivariate_challenge = {})
1315 {
1316 Utils::template accumulate_relation_evaluations_without_skipping<>(purported_evaluations,
1318 relation_parameters,
1319 gate_separators.partial_evaluation_result);
1320 FF main_factor{ 1 };
1321 FF offset_factor{ 0 };
1322 if constexpr (UseRowDisablingPolynomial<Flavor> && Flavor::HasZK) {
1323 main_factor = RowDisablingPolynomial<FF>::evaluate_at_challenge(multivariate_challenge,
1324 multivariate_challenge.size());
1325 offset_factor = FF{ 1 } - main_factor;
1326 }
1327 return Utils::scale_and_batch_elements(relation_evaluations, alphas, main_factor, offset_factor);
1328 }
1329
1334 void process_round(const std::shared_ptr<Transcript>& transcript,
1335 std::vector<FF>& multivariate_challenge,
1336 bb::GateSeparatorPolynomial<FF>& gate_separators,
1337 size_t round_idx)
1338 {
1339 const std::string round_univariate_comm_label = "Sumcheck:univariate_comm_" + std::to_string(round_idx);
1340 const std::string univariate_eval_label_0 = "Sumcheck:univariate_" + std::to_string(round_idx) + "_eval_0";
1341 const std::string univariate_eval_label_1 = "Sumcheck:univariate_" + std::to_string(round_idx) + "_eval_1";
1342
1343 // Receive the commitment to the round univariate
1344 round_univariate_commitments.push_back(
1345 transcript->template receive_from_prover<Commitment>(round_univariate_comm_label));
1346 // Receive evals at 0 and 1
1347 round_univariate_evaluations.push_back(
1348 { transcript->template receive_from_prover<FF>(univariate_eval_label_0),
1349 transcript->template receive_from_prover<FF>(univariate_eval_label_1),
1350 FF(0) }); // Third element will be populated in perform_final_verification
1351
1352 const FF round_challenge = transcript->template get_challenge<FF>("Sumcheck:u_" + std::to_string(round_idx));
1353 multivariate_challenge.emplace_back(round_challenge);
1354
1355 gate_separators.partially_evaluate(round_challenge);
1356
1357 // For Grumpkin, we don't perform per-round verification here
1358 // It will be deferred to the final check
1359 }
1360
1365 bool perform_final_verification(const FF& full_honk_purported_value)
1366 {
1367 // Compute the sum of evaluations at 0 and 1 for the first round
1368 FF first_sumcheck_round_evaluations_sum =
1369 round_univariate_evaluations[0][0] + round_univariate_evaluations[0][1];
1370
1371 bool verified = false;
1372 if constexpr (IsRecursiveFlavor<Flavor>) {
1373 if constexpr (IsGrumpkinFlavor<Flavor>) {
1374 first_sumcheck_round_evaluations_sum.self_reduce();
1375 target_total_sum.self_reduce();
1376 full_honk_purported_value.self_reduce();
1377 }
1378 verified = (first_sumcheck_round_evaluations_sum.get_value() == target_total_sum.get_value());
1379 first_sumcheck_round_evaluations_sum.assert_equal(target_total_sum);
1380 } else {
1381 verified = (first_sumcheck_round_evaluations_sum == target_total_sum);
1382 }
1383
1384 // Populate claimed evaluations of Sumcheck Round Univariates at the round challenges.
1385 // These will be checked as a part of Shplemini.
1386 for (size_t round_idx = 1; round_idx < round_univariate_evaluations.size(); round_idx++) {
1387 round_univariate_evaluations[round_idx - 1][2] =
1388 round_univariate_evaluations[round_idx][0] + round_univariate_evaluations[round_idx][1];
1389 }
1390
1391 // Store the final evaluation for Shplemini
1392 round_univariate_evaluations[round_univariate_evaluations.size() - 1][2] = full_honk_purported_value;
1393 return verified;
1394 }
1395
1399 std::vector<Commitment> get_round_univariate_commitments() { return round_univariate_commitments; }
1400
1404 std::vector<std::array<FF, 3>> get_round_univariate_evaluations() { return round_univariate_evaluations; }
1405};
1406} // namespace bb
#define BB_ASSERT(expression,...)
Definition assert.hpp:70
#define BB_BENCH_NAME(name)
Definition bb_bench.hpp:264
A base class labelling all entities (for instance, all of the polynomials used by the prover during s...
A field element for each entity of the flavor. These entities represent the prover polynomials evalua...
static constexpr bool HasZK
typename Curve::ScalarField FF
static constexpr size_t NUM_SUBRELATIONS
static constexpr size_t NUM_ALL_ENTITIES
static constexpr size_t MAX_PARTIAL_RELATION_LENGTH
typename G1::affine_element Commitment
static size_t row_skip_active_prefix_end(const ProverPolynomialsOrPartiallyEvaluatedMultivariates &polynomials)
static constexpr bool USE_SHORT_MONOMIALS
static constexpr size_t BATCHED_RELATION_PARTIAL_LENGTH
static constexpr size_t NUM_RELATIONS
BaseTranscript< Codec, HashFunction > Transcript
Relations_< FF > Relations
static constexpr size_t TRACE_OFFSET
A wrapper for Relations to expose methods used by the Sumcheck prover or verifier to add the contribu...
static void scale_univariates(auto &tuple, const SubrelationSeparators &subrelation_separators)
Scale Univariates, each representing a subrelation, by different challenges.
Definition utils.hpp:76
static void zero_elements(auto &tuple)
Set each element in a tuple of arrays to zero.
Definition utils.hpp:205
static void zero_univariates(auto &tuple)
Set all coefficients of Univariates to zero.
Definition utils.hpp:61
static constexpr void add_nested_tuples(Tuple &tuple_1, const Tuple &tuple_2)
Componentwise addition of nested tuples (tuples of tuples)
Definition utils.hpp:118
static FF scale_and_batch_elements(auto &tuple, const SubrelationSeparators &subrelation_separators, const FF &main_factor=FF{ 1 }, const FF &offset_factor=FF{ 0 })
Scale per-subrelation evaluations by α powers and row-disabling factors, then sum.
Definition utils.hpp:225
Lazy edge container for USE_SHORT_MONOMIALS flavors, generic over the lane element type.
void set_current_edge(const size_t edge_idx)
std::conditional_t< cache_on_heap, std::vector< bb::Univariate< Element, 2 > >, std::array< bb::Univariate< Element, 2 >, Flavor::NUM_ALL_ENTITIES > > Cache
std::bitset< Flavor::NUM_ALL_ENTITIES > materialized
LazyExtendedEdges(const Multivariates &multivariates)
typename Flavor::template ProverUnivariates< 2 >::EntityId EntityId
const bb::Univariate< Element, 2 > & operator[](const EntityId id) const
Imlementation of the Sumcheck prover round.
static size_t chunk_count(const size_t span, const size_t rows_per_chunk)
decltype(create_sumcheck_tuple_of_tuples_of_univariates< Relations >()) SumcheckTupleOfTuplesOfUnivariates
static constexpr size_t LIBRA_UNIVARIATES_LENGTH
static constexpr size_t BATCHED_RELATION_PARTIAL_LENGTH
The total algebraic degree of the Sumcheck relation as a polynomial in Prover Polynomials incremen...
std::conditional_t< Flavor::USE_SHORT_MONOMIALS, typename Flavor::template ProverUnivariates< 2 >, typename Flavor::ExtendedEdges > ExtendedEdges
static constexpr size_t ROWS_PER_CHUNK
static constexpr size_t MAX_PARTIAL_RELATION_LENGTH
The total algebraic degree of the Sumcheck relation as a polynomial in Prover Polynomials .
static constexpr bool USES_ROW_MANIFEST
decltype(create_sumcheck_tuple_of_tuples_of_univariates< RelationTupleFor< Element > >()) AccumulatorsFor
static constexpr bool HAS_STATIC_ROW_SKIP_MANIFEST
std::vector< EdgeRange > compute_edge_ranges(ProverPolynomialsOrPartiallyEvaluatedMultivariates &polynomials)
Compute the edge ranges the main sumcheck loop must visit.
std::vector< EdgeRange > compute_row_skip_edge_ranges(ProverPolynomialsOrPartiallyEvaluatedMultivariates &polynomials, const size_t effective_round_size) const
static void merge_edge_ranges(std::vector< EdgeRange > &ranges)
static Element element_scaling(const bb::GateSeparatorPolynomial< FF > &gate_separators, const size_t edge_idx)
static constexpr bool CAN_SKIP_ROWS
SumcheckRoundUnivariate compute_univariate(ProverPolynomialsOrPartiallyEvaluatedMultivariates &polynomials, const bb::RelationParameters< FF > &relation_parameters, const bb::GateSeparatorPolynomial< FF > &gate_separators, const SubrelationSeparators &alphas)
Return the evaluations of the round univariate at .
size_t compute_effective_round_size(const ProverPolynomialsOrPartiallyEvaluatedMultivariates &multivariates) const
Compute the effective round size by finding the maximum end_index() across witness polynomials.
void accumulate_relation_univariates(Accumulators &univariate_accumulators, const Edges &extended_edges, const bb::RelationParameters< Element > &relation_parameters, const Element &scaling_factor)
In Round , for a given point , calculate the contribution of each sub-relation to .
SumcheckRoundUnivariate compute_virtual_contribution(ProverPolynomialsOrPartiallyEvaluatedMultivariates &polynomials, const bb::RelationParameters< FF > &relation_parameters, const GateSeparatorPolynomial< FF > &gate_separator, const SubrelationSeparators &alphas, const RowDisablingPolynomial< FF > *row_disabling_polynomial=nullptr)
Virtual (zero-extension) round univariate contribution.
static auto make_extended_edges(const Multivariates &multivariates)
void advance_round()
Advance to the next regular sumcheck round: halve the active hypercube size and increment the round i...
SumcheckProverRound(size_t initial_round_size)
static void extend_edges(ExtendedEdges &extended_edges, const ProverPolynomialsOrPartiallyEvaluatedMultivariates &multivariates, const size_t edge_idx)
To compute the round univariate in Round , the prover first computes the values of Honk polynomials ...
static void reduce_accumulator(SumcheckTupleOfTuplesOfUnivariates &destination, const AccumulatorsFor< Element > &source)
auto make_edge_chunks(ProverPolynomialsOrPartiallyEvaluatedMultivariates &polynomials)
static void for_each_edge_group(const size_t begin, const size_t end, const size_t group_stride, GroupFn on_full_group, PairFn on_leftover_pair)
SumcheckTupleOfTuplesOfUnivariates univariate_accumulators
static void load_edge(Edges &edges, const Multivariates &multivariates, const size_t edge_idx)
typename Flavor::template Relations_< Element > RelationTupleFor
std::array< FF, Flavor::NUM_SUBRELATIONS - 1 > SubrelationSeparators
void accumulate_edge(Accumulators &accumulator, Edges &edge_container, const Multivariates &polynomials, const bb::RelationParameters< Element > &params, const bb::GateSeparatorPolynomial< FF > &gate_separators, const size_t edge_idx)
static SumcheckRoundUnivariate compute_libra_univariate(const ZKData &zk_sumcheck_data, size_t round_idx)
Compute Libra round univariate expressed given by the formula.
typename Flavor::FF FF
bool is_virtual_round() const
A virtual (zero-extension) round is any round at or beyond the multivariate_d regular rounds....
static constexpr size_t NUM_RELATIONS
Number of batched sub-relations in specified by Flavor.
static size_t round_up_to_even(const size_t value)
static constexpr bool USE_LAZY_EDGES
typename Flavor::Relations Relations
size_t round_size
In regular round i = 0,...,multivariate_d-1, equals 2^{multivariate_d - i}; halved once per regular r...
void accumulate_relation_univariates_public(SumcheckTupleOfTuplesOfUnivariates &univariate_accumulators, const auto &extended_edges, const bb::RelationParameters< FF > &relation_parameters, const FF &scaling_factor)
void accumulate_edge_chunks(EdgeChunks &chunks, ProverPolynomialsOrPartiallyEvaluatedMultivariates &polynomials, const bb::RelationParameters< FF > &relation_parameters, const bb::GateSeparatorPolynomial< FF > &gate_separators)
static void extend_and_batch_univariates(const TupleOfTuplesOfUnivariates &tuple, ExtendedUnivariate &result, const bb::GateSeparatorPolynomial< FF > &gate_separators, const RowDisablingPolynomial< FF > *row_disabling_polynomial=nullptr)
Extend Univariates then sum them multiplying by the current -contributions.
static ExtendedUnivariate batch_over_relations(ContainerOverSubrelations &univariate_accumulators, const SubrelationSeparators &challenge, const bb::GateSeparatorPolynomial< FF > &gate_separators, const RowDisablingPolynomial< FF > *row_disabling_polynomial=nullptr)
Given a tuple of tuples of extended per-relation contributions, and a challenge ,...
static constexpr bool USE_LAZY_SHORT_EDGES
SumcheckRoundUnivariate compute_offset_area_contribution(ProverPolynomialsOrPartiallyEvaluatedMultivariates &polynomials, const bb::RelationParameters< FF > &relation_parameters, const bb::GateSeparatorPolynomial< FF > &gate_separators, const SubrelationSeparators &alphas, const RowDisablingPolynomial< FF > row_disabling_polynomial)
Contribution to the round univariate from the offset-area head rows (rows 0 .. TRACE_OFFSET - 1),...
static void append_edge_range(std::vector< EdgeRange > &ranges, const size_t start, const size_t end)
std::vector< std::array< FF, 3 > > round_univariate_evaluations
typename std::vector< FF > ClaimedLibraEvaluations
std::vector< Commitment > round_univariate_commitments
void process_round(const std::shared_ptr< Transcript > &transcript, std::vector< FF > &multivariate_challenge, bb::GateSeparatorPolynomial< FF > &gate_separators, size_t round_idx)
Process a single sumcheck round for Grumpkin: receive commitment and evaluations, defer per-round ver...
decltype(create_tuple_of_arrays_of_values< typename Flavor::Relations >()) TupleOfArraysOfValues
FF compute_full_relation_purported_value(const ClaimedEvaluations &purported_evaluations, const bb::RelationParameters< FF > &relation_parameters, const bb::GateSeparatorPolynomial< FF > &gate_separators, const SubrelationSeparators &alphas, std::span< const FF > multivariate_challenge={})
Evaluate the full Honk relation at the sumcheck challenge u (Grumpkin variant).
bool perform_final_verification(const FF &full_honk_purported_value)
Perform final verification for Grumpkin: check first round sum, populate Shplemini data,...
std::array< FF, Flavor::NUM_SUBRELATIONS - 1 > SubrelationSeparators
std::vector< std::array< FF, 3 > > get_round_univariate_evaluations()
Get round univariate evaluations for Shplemini.
std::vector< Commitment > get_round_univariate_commitments()
Get round univariate commitments for Shplemini.
typename Flavor::AllValues ClaimedEvaluations
Implementation of the Sumcheck Verifier Round.
typename Flavor::Commitment Commitment
typename Flavor::Relations Relations
typename std::vector< FF > ClaimedLibraEvaluations
std::vector< std::array< FF, 3 > > get_round_univariate_evaluations()
Get round univariate evaluations (only used for Grumpkin flavors).
void check_sum(bb::Univariate< FF, BATCHED_RELATION_PARTIAL_LENGTH > &univariate)
Check that the round target sum is correct.
static constexpr size_t NUM_RELATIONS
void process_round(const std::shared_ptr< Transcript > &transcript, std::vector< FF > &multivariate_challenge, bb::GateSeparatorPolynomial< FF > &gate_separators, size_t round_idx)
Process a single sumcheck round: receive univariate from transcript, verify sum, generate challenge.
decltype(create_tuple_of_arrays_of_values< typename Flavor::Relations >()) TupleOfArraysOfValues
std::array< FF, Flavor::NUM_SUBRELATIONS - 1 > SubrelationSeparators
FF compute_full_relation_purported_value(const ClaimedEvaluations &purported_evaluations, const bb::RelationParameters< FF > &relation_parameters, const bb::GateSeparatorPolynomial< FF > &gate_separators, const SubrelationSeparators &alphas, std::span< const FF > multivariate_challenge={})
Evaluate the full Honk relation at the sumcheck challenge u.
typename Flavor::AllValues ClaimedEvaluations
TupleOfArraysOfValues relation_evaluations
std::vector< Commitment > get_round_univariate_commitments()
Get round univariate commitments (only used for Grumpkin flavors).
bool perform_final_verification(const FF &full_honk_purported_value)
Perform final verification: check that the computed target sum matches the full relation evaluation....
typename Flavor::Transcript Transcript
void compute_next_target_sum(bb::Univariate< FF, BATCHED_RELATION_PARTIAL_LENGTH > &univariate, FF &round_challenge)
Compute the next target sum.
SumcheckVerifierRound(FF target_total_sum=0)
static constexpr size_t BATCHED_RELATION_PARTIAL_LENGTH
Fr & value_at(size_t i)
static Univariate zero()
std::array< Fr, LENGTH > evaluations
Fr evaluate(const Fr &u) const
Evaluate a univariate at a point u not known at compile time and assumed not to be in the domain (els...
A relation is "offset-only" if its contribution enters the round univariate scaled by L(x) = L_0 + L_...
Check if the flavor has a static skip method to determine if accumulation of all relations can be ski...
The templates defined herein facilitate sharing the relation arithmetic between the prover and the ve...
Base class templates shared across Honk flavors.
constexpr T get_msb(const T in)
Definition get_msb.hpp:50
constexpr size_t FF_COPY_COST
Definition thread.hpp:144
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
constexpr size_t lane_count< T >
size_t get_num_cpus()
Definition thread.cpp:34
constexpr void constexpr_for(F &&f)
Implements a loop using a compile-time iterator. Requires c++20. Implementation (and description) fro...
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 size_t lane_count
void parallel_for(size_t num_iterations, const std::function< void(size_t)> &func)
Definition thread.cpp:112
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::string to_string(bb::avm2::ValueTag tag)
bb::VectorAffineElementPushSpan< BaseParams > lhs
bb::VectorAffineElementPushSpan< BaseParams > rhs
Curve::Element Element
FF current_element() const
Computes the component at index current_element_idx in betas.
void partially_evaluate(FF challenge)
Partially evaluate the -polynomial at the new challenge and update .
FF partial_evaluation_result
The value obtained by partially evaluating one variable in the power polynomial at each round....
Container for parameters used by the grand product (permutation, lookup) Honk relations.
Polynomial for Sumcheck with disabled Rows.
static FF evaluate_at_challenge(std::span< const FF > multivariate_challenge, const size_t log_circuit_size)
Compute the evaluation of at the sumcheck challenge.
ContiguousEdgeChunks(const size_t begin, const size_t end, const size_t rows_per_chunk)
ListedEdgeChunks(const std::vector< EdgeRange > &ranges, const size_t rows_per_chunk)
size_t thread_index
Definition thread.hpp:150
auto range(size_t size, size_t offset=0) const
Definition thread.hpp:152
This structure is created to contain various polynomials and constants required by ZK Sumcheck.
std::vector< Polynomial< FF > > libra_univariates
VectorField result