Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
scalar_multiplication.cpp
Go to the documentation of this file.
1// === AUDIT STATUS ===
2// internal: { status: Planned, auditors: [Sergei], commit: }
3// external_1: { status: not started, auditors: [], commit: }
4// external_2: { status: not started, auditors: [], commit: }
5// =====================
9
10#include "./process_buckets.hpp"
18
21
23
24// Naive double-and-add fallback for small inputs (< PIPPENGER_THRESHOLD points).
25template <typename Curve> typename Curve::Element small_mul(const typename MSM<Curve>::MSMData& msm_data) noexcept
26{
27 const auto& scalars = msm_data.scalars;
28 const auto& points = msm_data.points;
29 const auto& scalar_indices = msm_data.scalar_indices;
30 const size_t range = scalar_indices.size();
31
32 typename Curve::Element r = Curve::Group::point_at_infinity;
33 for (size_t i = 0; i < range; ++i) {
34 typename Curve::Element f = points[scalar_indices[i]];
35 r += f * scalars[scalar_indices[i]].to_montgomery_form();
36 }
37 return r;
38}
39
40template <typename Curve>
42 std::vector<uint32_t>& nonzero_scalar_indices) noexcept
43{
45
46 // Pass 1: Each thread converts from Montgomery and collects nonzero indices into its own vector
47 parallel_for([&](const ThreadChunk& chunk) {
48 BB_ASSERT_EQ(chunk.total_threads, thread_indices.size());
49 auto range = chunk.range(scalars.size());
50 if (range.empty()) {
51 return;
52 }
53 std::vector<uint32_t>& thread_scalar_indices = thread_indices[chunk.thread_index];
54 thread_scalar_indices.reserve(range.size());
55 for (size_t i : range) {
56 BB_ASSERT_DEBUG(i < scalars.size());
57 auto& scalar = scalars[i];
58 scalar.self_from_montgomery_form();
59
60 if (!scalar.is_zero()) {
61 thread_scalar_indices.push_back(static_cast<uint32_t>(i));
62 }
63 }
64 });
65
66 size_t num_entries = 0;
67 for (const auto& indices : thread_indices) {
68 num_entries += indices.size();
69 }
70 nonzero_scalar_indices.resize(num_entries);
71
72 // Pass 2: Copy each thread's indices to the output vector (no branching)
73 parallel_for([&](const ThreadChunk& chunk) {
74 BB_ASSERT_EQ(chunk.total_threads, thread_indices.size());
75 size_t offset = 0;
76 for (size_t i = 0; i < chunk.thread_index; ++i) {
77 offset += thread_indices[i].size();
78 }
79 for (size_t i = offset; i < offset + thread_indices[chunk.thread_index].size(); ++i) {
80 nonzero_scalar_indices[i] = thread_indices[chunk.thread_index][i - offset];
81 }
82 });
83}
84
85template <typename Curve>
87 std::span<std::span<ScalarField>> scalars, std::vector<std::vector<uint32_t>>& msm_scalar_indices) noexcept
88{
89
90 const size_t num_msms = scalars.size();
91 msm_scalar_indices.resize(num_msms);
92 for (size_t i = 0; i < num_msms; ++i) {
93 transform_scalar_and_get_nonzero_scalar_indices(scalars[i], msm_scalar_indices[i]);
94 }
95
96 size_t total_work = 0;
97 for (const auto& indices : msm_scalar_indices) {
98 total_work += indices.size();
99 }
100
101 const size_t num_threads = get_num_cpus();
102 std::vector<ThreadWorkUnits> work_units(num_threads);
103
104 const size_t work_per_thread = numeric::ceil_div(total_work, num_threads);
105 const size_t work_of_last_thread = total_work - (work_per_thread * (num_threads - 1));
106
107 // Only use a single work unit if we don't have enough work for every thread
108 if (num_threads > total_work) {
109 for (size_t i = 0; i < num_msms; ++i) {
110 work_units[0].push_back(MSMWorkUnit{
111 .batch_msm_index = i,
112 .start_index = 0,
113 .size = msm_scalar_indices[i].size(),
114 });
115 }
116 return work_units;
117 }
118
119 size_t thread_accumulated_work = 0;
120 size_t current_thread_idx = 0;
121 for (size_t i = 0; i < num_msms; ++i) {
122 size_t msm_work_remaining = msm_scalar_indices[i].size();
123 const size_t initial_msm_work = msm_work_remaining;
124
125 while (msm_work_remaining > 0) {
126 BB_ASSERT_LT(current_thread_idx, work_units.size());
127
128 const size_t total_thread_work =
129 (current_thread_idx == num_threads - 1) ? work_of_last_thread : work_per_thread;
130 const size_t available_thread_work = total_thread_work - thread_accumulated_work;
131 const size_t work_to_assign = std::min(available_thread_work, msm_work_remaining);
132
133 work_units[current_thread_idx].push_back(MSMWorkUnit{
134 .batch_msm_index = i,
135 .start_index = initial_msm_work - msm_work_remaining,
136 .size = work_to_assign,
137 });
138
139 thread_accumulated_work += work_to_assign;
140 msm_work_remaining -= work_to_assign;
141
142 // Move to next thread if current thread is full
143 if (thread_accumulated_work >= total_thread_work) {
144 current_thread_idx++;
145 thread_accumulated_work = 0;
146 }
147 }
148 }
149 return work_units;
150}
151
167template <typename Curve>
168uint32_t MSM<Curve>::get_scalar_slice(const typename Curve::ScalarField& scalar,
169 size_t round,
170 size_t slice_size) noexcept
171{
172 constexpr size_t LIMB_BITS = 64;
173
174 size_t hi_bit = NUM_BITS_IN_FIELD - (round * slice_size);
175 size_t lo_bit = (hi_bit < slice_size) ? 0 : hi_bit - slice_size;
176
177 BB_ASSERT_DEBUG(lo_bit < hi_bit);
178 BB_ASSERT_DEBUG(hi_bit <= NUM_BITS_IN_FIELD); // Ensures hi_bit < 256, so end_limb <= 3
179
180 size_t start_limb = lo_bit / LIMB_BITS;
181 size_t end_limb = hi_bit / LIMB_BITS;
182 size_t lo_slice_offset = lo_bit & (LIMB_BITS - 1);
183 size_t actual_slice_size = hi_bit - lo_bit;
184 size_t lo_slice_bits =
185 (LIMB_BITS - lo_slice_offset < actual_slice_size) ? (LIMB_BITS - lo_slice_offset) : actual_slice_size;
186 size_t hi_slice_bits = actual_slice_size - lo_slice_bits;
187
188 uint64_t lo_slice = (scalar.data[start_limb] >> lo_slice_offset) & ((1ULL << lo_slice_bits) - 1);
189 uint64_t hi_slice = (start_limb != end_limb) ? (scalar.data[end_limb] & ((1ULL << hi_slice_bits) - 1)) : 0;
190
191 return static_cast<uint32_t>(lo_slice | (hi_slice << lo_slice_bits));
192}
193
194template <typename Curve> uint32_t MSM<Curve>::get_optimal_log_num_buckets(const size_t num_points) noexcept
195{
196 // Cost model: total_cost = num_rounds * (num_points + num_buckets * BUCKET_ACCUMULATION_COST)
197 auto compute_cost = [&](uint32_t bits) {
198 size_t rounds = numeric::ceil_div(NUM_BITS_IN_FIELD, static_cast<size_t>(bits));
199 size_t buckets = size_t{ 1 } << bits;
200 return rounds * (num_points + buckets * BUCKET_ACCUMULATION_COST);
201 };
202
203 uint32_t best_bits = 1;
204 size_t best_cost = compute_cost(1);
205 for (uint32_t bits = 2; bits < MAX_SLICE_BITS; ++bits) {
206 size_t cost = compute_cost(bits);
207 if (cost < best_cost) {
208 best_cost = cost;
209 best_bits = bits;
210 }
211 }
212 return best_bits;
213}
214
215template <typename Curve> bool MSM<Curve>::use_affine_trick(const size_t num_points, const size_t num_buckets) noexcept
216{
217 if (num_points < AFFINE_TRICK_THRESHOLD) {
218 return false;
219 }
220
221 // Affine trick requires log(N) modular inversions per Pippenger round.
222 // It saves num_points * AFFINE_TRICK_SAVINGS_PER_OP field muls, plus
223 // num_buckets * JACOBIAN_Z_NOT_ONE_PENALTY field muls (buckets have Z=1 with affine trick)
224
225 // Cost of modular inversion via exponentiation:
226 // - NUM_BITS_IN_FIELD squarings
227 // - (NUM_BITS_IN_FIELD + 3) / 4 multiplications (4-bit windows)
228 // - INVERSION_TABLE_COST multiplications for lookup table
229 constexpr size_t COST_OF_INVERSION = NUM_BITS_IN_FIELD + ((NUM_BITS_IN_FIELD + 3) / 4) + INVERSION_TABLE_COST;
230
231 double log2_num_points = log2(static_cast<double>(num_points));
232 size_t savings_per_round = (num_points * AFFINE_TRICK_SAVINGS_PER_OP) + (num_buckets * JACOBIAN_Z_NOT_ONE_PENALTY);
233 double inversion_cost_per_round = log2_num_points * static_cast<double>(COST_OF_INVERSION);
234
235 return static_cast<double>(savings_per_round) > inversion_cost_per_round;
236}
237
238template <typename Curve>
240 const size_t num_points,
241 typename Curve::BaseField* scratch_space) noexcept
242{
243 using AffineElement = typename Curve::AffineElement;
244 using BaseField = typename Curve::BaseField;
245
246 // Use interleaved array policy: pairs are (points[2i], points[2i+1]), output in points[num_pairs + 1]
247 // This includes prefetching for non-sequential output access
248 const size_t num_pairs = num_points / 2;
249 bb::group_elements::batch_affine_add_impl<bb::group_elements::InterleavedArrayPolicy, AffineElement, BaseField>(
250 points, points, num_pairs, scratch_space);
251}
252
253template <typename Curve>
255{
256 const size_t size = msm_data.scalar_indices.size();
257 const uint32_t bits_per_slice = get_optimal_log_num_buckets(size);
258 const size_t num_buckets = size_t{ 1 } << bits_per_slice;
259 const uint32_t num_rounds = static_cast<uint32_t>((NUM_BITS_IN_FIELD + bits_per_slice - 1) / bits_per_slice);
260 const uint32_t remainder = NUM_BITS_IN_FIELD % bits_per_slice;
261
262 JacobianBucketAccumulators bucket_data(num_buckets);
263 Element msm_result = Curve::Group::point_at_infinity;
264
265 for (uint32_t round = 0; round < num_rounds; ++round) {
266 // Populate buckets using Jacobian accumulation
267 for (size_t i = 0; i < size; ++i) {
268 uint32_t idx = msm_data.scalar_indices[i];
269 uint32_t bucket = get_scalar_slice(msm_data.scalars[idx], round, bits_per_slice);
270 if (bucket > 0) {
271 if (bucket_data.bucket_exists.get(bucket)) {
272 bucket_data.buckets[bucket] += msm_data.points[idx];
273 } else {
274 bucket_data.buckets[bucket] = msm_data.points[idx];
275 bucket_data.bucket_exists.set(bucket, true);
276 }
277 }
278 }
279
280 // Reduce buckets and accumulate into result
281 Element bucket_result = accumulate_buckets(bucket_data);
282 bucket_data.bucket_exists.clear();
283
284 uint32_t num_doublings = (round == num_rounds - 1 && remainder != 0) ? remainder : bits_per_slice;
285 for (uint32_t i = 0; i < num_doublings; ++i) {
286 msm_result.self_dbl();
287 }
288 msm_result += bucket_result;
289 }
290 return msm_result;
291}
292
293template <typename Curve>
295{
296 const size_t num_points = msm_data.scalar_indices.size();
297 const uint32_t bits_per_slice = get_optimal_log_num_buckets(num_points);
298 const size_t num_buckets = size_t{ 1 } << bits_per_slice;
299
300 if (!use_affine_trick(num_points, num_buckets)) {
301 return jacobian_pippenger_with_transformed_scalars(msm_data);
302 }
303
304 const uint32_t num_rounds = static_cast<uint32_t>((NUM_BITS_IN_FIELD + bits_per_slice - 1) / bits_per_slice);
305 const uint32_t remainder = NUM_BITS_IN_FIELD % bits_per_slice;
306
307 // Per-call allocation for WASM compatibility (thread_local causes issues in WASM)
308 AffineAdditionData affine_data;
309 BucketAccumulators bucket_data(num_buckets);
310
311 Element msm_result = Curve::Group::point_at_infinity;
312
313 for (uint32_t round = 0; round < num_rounds; ++round) {
314 // Build point schedule for this round
315 {
316 for (size_t i = 0; i < num_points; ++i) {
317 uint32_t idx = msm_data.scalar_indices[i];
318 uint32_t bucket_idx = get_scalar_slice(msm_data.scalars[idx], round, bits_per_slice);
319 msm_data.point_schedule[i] = PointScheduleEntry::create(idx, bucket_idx).data;
320 }
321 }
322
323 // Sort by bucket and count zero-bucket entries
324 size_t num_zero_bucket_entries =
325 sort_point_schedule_and_count_zero_buckets(&msm_data.point_schedule[0], num_points, bits_per_slice);
326 size_t round_size = num_points - num_zero_bucket_entries;
327
328 // Accumulate points into buckets
329 Element bucket_result = Curve::Group::point_at_infinity;
330 if (round_size > 0) {
331 std::span<uint64_t> schedule(&msm_data.point_schedule[num_zero_bucket_entries], round_size);
332 batch_accumulate_points_into_buckets(schedule, msm_data.points, affine_data, bucket_data);
333 bucket_result = accumulate_buckets(bucket_data);
334 bucket_data.bucket_exists.clear();
335 }
336
337 // Combine into running result
338 uint32_t num_doublings = (round == num_rounds - 1 && remainder != 0) ? remainder : bits_per_slice;
339 for (uint32_t i = 0; i < num_doublings; ++i) {
340 msm_result.self_dbl();
341 }
342 msm_result += bucket_result;
343 }
344
345 return msm_result;
346}
347
348template <typename Curve>
352 MSM<Curve>::BucketAccumulators& bucket_data) noexcept
353{
354
355 if (point_schedule.empty()) {
356 return;
357 }
358
359 size_t point_it = 0;
360 size_t scratch_it = 0;
361 const size_t num_points = point_schedule.size();
362 const size_t prefetch_max = (num_points >= PREFETCH_LOOKAHEAD) ? (num_points - PREFETCH_LOOKAHEAD) : 0;
363 const size_t last_index = num_points - 1;
364
365 // Iterative loop - continues until all points processed and no work remains in scratch space
366 while (point_it < num_points || scratch_it != 0) {
367 // Step 1: Fill scratch space with up to BATCH_SIZE/2 independent additions
368 while (((scratch_it + 1) < AffineAdditionData::BATCH_SIZE) && (point_it < last_index)) {
369 // Prefetch points we'll need soon (every PREFETCH_INTERVAL iterations)
370 if ((point_it < prefetch_max) && ((point_it & PREFETCH_INTERVAL_MASK) == 0)) {
371 for (size_t i = PREFETCH_LOOKAHEAD / 2; i < PREFETCH_LOOKAHEAD; ++i) {
372 PointScheduleEntry entry{ point_schedule[point_it + i] };
373 __builtin_prefetch(&points[entry.point_index()]);
374 }
375 }
376
377 PointScheduleEntry lhs{ point_schedule[point_it] };
378 PointScheduleEntry rhs{ point_schedule[point_it + 1] };
379
380 process_bucket_pair(lhs.bucket_index(),
381 rhs.bucket_index(),
382 &points[lhs.point_index()],
383 &points[rhs.point_index()],
384 affine_data,
385 bucket_data,
386 scratch_it,
387 point_it);
388 }
389
390 // Handle the last point (odd count case) - separate to avoid bounds check on point_schedule[point_it + 1]
391 if (point_it == last_index) {
392 PointScheduleEntry last{ point_schedule[point_it] };
393 process_single_point(
394 last.bucket_index(), &points[last.point_index()], affine_data, bucket_data, scratch_it, point_it);
395 }
396
397 // Compute independent additions using Montgomery's batch inversion trick
398 size_t num_points_to_add = scratch_it;
399 if (num_points_to_add >= 2) {
400 add_affine_points(
401 affine_data.points_to_add.data(), num_points_to_add, affine_data.inversion_scratch_space.data());
402 }
403
404 // add_affine_points stores results in the top-half of scratch space
405 AffineElement* affine_output = affine_data.points_to_add.data() + (num_points_to_add / 2);
406
407 // Recirculate addition outputs back into scratch space or bucket accumulators
408 size_t new_scratch_it = 0;
409 size_t output_it = 0;
410 size_t num_outputs = num_points_to_add / 2;
411
412 while ((num_outputs > 1) && (output_it + 1 < num_outputs)) {
413 uint32_t lhs_bucket = affine_data.addition_result_bucket_destinations[output_it];
414 uint32_t rhs_bucket = affine_data.addition_result_bucket_destinations[output_it + 1];
415
416 process_bucket_pair(lhs_bucket,
417 rhs_bucket,
418 &affine_output[output_it],
419 &affine_output[output_it + 1],
420 affine_data,
421 bucket_data,
422 new_scratch_it,
423 output_it);
424 }
425
426 // Handle the last output (odd count case)
427 if (num_outputs > 0 && output_it == num_outputs - 1) {
428 uint32_t bucket = affine_data.addition_result_bucket_destinations[output_it];
429 process_single_point(
430 bucket, &affine_output[output_it], affine_data, bucket_data, new_scratch_it, output_it);
431 }
432
433 // Continue with recirculated points
434 scratch_it = new_scratch_it;
435 }
436}
437
438template <typename Curve>
441 std::span<std::span<ScalarField>> scalars,
442 bool handle_edge_cases) noexcept
443{
444 BB_ASSERT_EQ(points.size(), scalars.size());
445 const size_t num_msms = points.size();
446
447 std::vector<std::vector<uint32_t>> msm_scalar_indices;
448 std::vector<ThreadWorkUnits> thread_work_units = get_work_units(scalars, msm_scalar_indices);
449 const size_t num_cpus = get_num_cpus();
450 std::vector<std::vector<std::pair<Element, size_t>>> thread_msm_results(num_cpus);
451 BB_ASSERT_EQ(thread_work_units.size(), num_cpus);
452
453 // Select Pippenger implementation once (hoisting branch outside hot loop)
454 // Jacobian: safe, handles edge cases | Affine: faster, assumes linearly independent points
455 auto pippenger_impl =
456 handle_edge_cases ? jacobian_pippenger_with_transformed_scalars : affine_pippenger_with_transformed_scalars;
457
458 // Once we have our work units, each thread can independently evaluate its assigned msms
459 parallel_for(num_cpus, [&](size_t thread_idx) {
460 if (!thread_work_units[thread_idx].empty()) {
461 const std::vector<MSMWorkUnit>& msms = thread_work_units[thread_idx];
462 std::vector<std::pair<Element, size_t>>& msm_results = thread_msm_results[thread_idx];
463 msm_results.reserve(msms.size());
464
465 // Point schedule buffer for this thread - avoids per-work-unit heap allocation
466 std::vector<uint64_t> point_schedule_buffer;
467
468 for (const MSMWorkUnit& msm : msms) {
469 point_schedule_buffer.resize(msm.size);
470 MSMData msm_data =
471 MSMData::from_work_unit(scalars, points, msm_scalar_indices, point_schedule_buffer, msm);
472 Element msm_result =
473 (msm.size < PIPPENGER_THRESHOLD) ? small_mul<Curve>(msm_data) : pippenger_impl(msm_data);
474
475 msm_results.emplace_back(msm_result, msm.batch_msm_index);
476 }
477 }
478 });
479
480 // Accumulate results. This part needs to be single threaded, but amount of work done here should be small
481 // TODO(@zac-williamson) check this? E.g. if we are doing a 2^16 MSM with 256 threads this single-threaded part
482 // will be painful.
483 std::vector<Element> results(num_msms, Curve::Group::point_at_infinity);
484 for (const auto& single_thread_msm_results : thread_msm_results) {
485 for (const auto& [element, index] : single_thread_msm_results) {
486 results[index] += element;
487 }
488 }
489 Element::batch_normalize(results.data(), num_msms);
490
491 // Convert scalars back TO Montgomery form so they remain unchanged from caller's perspective
492 for (auto& scalar_span : scalars) {
493 parallel_for_range(scalar_span.size(), [&](size_t start, size_t end) {
494 for (size_t i = start; i < end; ++i) {
495 scalar_span[i].self_to_montgomery_form();
496 }
497 });
498 }
499
500 return std::vector<AffineElement>(results.begin(), results.end());
501}
502
503template <typename Curve>
506 bool handle_edge_cases) noexcept
507{
508 if (scalars.size() == 0) {
509 return Curve::Group::affine_point_at_infinity;
510 }
511 const size_t num_scalars = scalars.size();
512 BB_ASSERT_GTE(points.size(), scalars.start_index + num_scalars);
513
514 // const_cast is safe: we convert from Montgomery, compute, then convert back.
515 // Scalars are unchanged from the caller's perspective.
516 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
517 ScalarField* scalar_ptr = const_cast<ScalarField*>(&scalars[scalars.start_index]);
518 std::span<ScalarField> scalar_span(scalar_ptr, num_scalars);
519
520 // Wrap into a size-1 batch and delegate to the general method that properly handles multi-threading
521 std::array<std::span<const AffineElement>, 1> points_batch{ points.subspan(scalars.start_index) };
522 std::array<std::span<ScalarField>, 1> scalars_batch{ scalar_span };
523
524 auto results = batch_multi_scalar_mul(std::span(points_batch), std::span(scalars_batch), handle_edge_cases);
525 return results[0];
526}
527
528template <typename Curve>
531 [[maybe_unused]] bool handle_edge_cases) noexcept
532{
533 return MSM<Curve>::msm(points, scalars, handle_edge_cases);
534}
535
536template <typename Curve>
542
545 bool handle_edge_cases = true) noexcept;
546
547template curve::Grumpkin::Element pippenger_unsafe<curve::Grumpkin>(
548 PolynomialSpan<const curve::Grumpkin::ScalarField> scalars, std::span<const curve::Grumpkin::AffineElement> points);
549
550template curve::BN254::Element pippenger<curve::BN254>(PolynomialSpan<const curve::BN254::ScalarField> scalars,
551 std::span<const curve::BN254::AffineElement> points,
552 bool handle_edge_cases = true);
553
554template curve::BN254::Element pippenger_unsafe<curve::BN254>(PolynomialSpan<const curve::BN254::ScalarField> scalars,
555 std::span<const curve::BN254::AffineElement> points);
556
557} // namespace bb::scalar_multiplication
558
559template class bb::scalar_multiplication::MSM<bb::curve::Grumpkin>;
560template class bb::scalar_multiplication::MSM<bb::curve::BN254>;
#define BB_ASSERT_GTE(left, right,...)
Definition assert.hpp:128
#define BB_ASSERT_DEBUG(expression,...)
Definition assert.hpp:55
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:83
#define BB_ASSERT_LT(left, right,...)
Definition assert.hpp:143
BB_INLINE bool get(size_t index) const noexcept
Definition bitvector.hpp:42
BB_INLINE void set(size_t index, bool value) noexcept
Definition bitvector.hpp:28
void clear()
Definition bitvector.hpp:50
typename Group::element Element
Definition grumpkin.hpp:62
typename Group::affine_element AffineElement
Definition grumpkin.hpp:63
typename Curve::BaseField BaseField
static bool use_affine_trick(size_t num_points, size_t num_buckets) noexcept
Decide if batch inversion saves work vs Jacobian additions.
static Element jacobian_pippenger_with_transformed_scalars(MSMData &msm_data) noexcept
Pippenger using Jacobian buckets (handles edge cases: doubling, infinity)
static uint32_t get_scalar_slice(const ScalarField &scalar, size_t round, size_t slice_size) noexcept
Extract c-bit slice from scalar for bucket index computation.
static Element affine_pippenger_with_transformed_scalars(MSMData &msm_data) noexcept
Pippenger using affine buckets with batch inversion (faster, no edge case handling)
static void add_affine_points(AffineElement *points, const size_t num_points, typename Curve::BaseField *scratch_space) noexcept
Batch add n/2 independent point pairs using Montgomery's trick.
static std::vector< ThreadWorkUnits > get_work_units(std::span< std::span< ScalarField > > scalars, std::vector< std::vector< uint32_t > > &msm_scalar_indices) noexcept
Distribute multiple MSMs across threads with balanced point counts.
static uint32_t get_optimal_log_num_buckets(size_t num_points) noexcept
Compute optimal bits per slice by minimizing cost over c in [1, MAX_SLICE_BITS)
static std::vector< AffineElement > batch_multi_scalar_mul(std::span< std::span< const AffineElement > > points, std::span< std::span< ScalarField > > scalars, bool handle_edge_cases=true) noexcept
Compute multiple MSMs in parallel with work balancing.
static void batch_accumulate_points_into_buckets(std::span< const uint64_t > point_schedule, std::span< const AffineElement > points, AffineAdditionData &affine_data, BucketAccumulators &bucket_data) noexcept
Process sorted point schedule into bucket accumulators using batched affine additions.
typename Curve::ScalarField ScalarField
typename Curve::AffineElement AffineElement
static void transform_scalar_and_get_nonzero_scalar_indices(std::span< ScalarField > scalars, std::vector< uint32_t > &nonzero_scalar_indices) noexcept
Convert scalars from Montgomery form and collect indices of nonzero scalars.
ssize_t offset
Definition engine.cpp:46
constexpr T ceil_div(const T &numerator, const T &denominator)
Computes the ceiling of the division of two integral types.
Definition general.hpp:23
Curve::Element small_mul(const typename MSM< Curve >::MSMData &msm_data) noexcept
Curve::Element pippenger(PolynomialSpan< const typename Curve::ScalarField > scalars, std::span< const typename Curve::AffineElement > points, bool handle_edge_cases) noexcept
Safe MSM wrapper (defaults to handle_edge_cases=true)
size_t sort_point_schedule_and_count_zero_buckets(uint64_t *point_schedule, const size_t num_entries, const uint32_t bucket_index_bits) noexcept
Sort point schedule by bucket index and count zero-bucket entries.
Curve::Element pippenger_unsafe(PolynomialSpan< const typename Curve::ScalarField > scalars, std::span< const typename Curve::AffineElement > points) noexcept
Fast MSM wrapper for linearly independent points (no edge case handling)
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
size_t get_num_cpus()
Definition thread.cpp:33
void parallel_for(size_t num_iterations, const std::function< void(size_t)> &func)
Definition thread.cpp:111
void parallel_for_range(size_t num_points, const std::function< void(size_t, size_t)> &func, size_t no_multhreading_if_less_or_equal)
Split a loop into several loops running in parallel.
Definition thread.cpp:141
STL namespace.
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
Curve::Element Element
size_t total_threads
Definition thread.hpp:151
size_t thread_index
Definition thread.hpp:150
auto range(size_t size, size_t offset=0) const
Definition thread.hpp:152
Scratch space for batched affine point additions (one per thread)
Affine bucket accumulators for the fast affine-trick Pippenger variant.
Jacobian bucket accumulators for the safe Pippenger variant.
Container for MSM input data passed between algorithm stages.
MSMWorkUnit describes an MSM that may be part of a larger MSM.
Packed point schedule entry: (point_index << 32) | bucket_index.