Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
transcript_builder.hpp
Go to the documentation of this file.
1// === AUDIT STATUS ===
2// internal: { status: Complete, auditors: [Raju], commit: 2a49eb6 }
3// external_1: { status: not started, auditors: [], commit: }
4// external_2: { status: not started, auditors: [], commit: }
5// =====================
6
7#pragma once
8
12
13namespace bb {
14
16 public:
19 using Element = typename CycleGroup::element;
21 using Accumulator = typename std::vector<Element>;
22
25 // These fields are populated in the first loop
27
28 bool transcript_msm_infinity = false; // are we at the end of an MSM *and* is the output the point at infinity?
29 bool accumulator_not_empty = false; // not(is the accumulator either empty or point-at-infinity?)
30 bool q_add = false;
31 bool q_mul = false;
32 bool q_eq = false;
33 bool q_reset_accumulator = false;
34 bool msm_transition = false;
35 uint32_t pc = 0;
36 uint32_t msm_count = 0; // number of multiplications in the MSM *up until and not including* this step.
38 false; // is the number of scalar muls we have completed at the end of our "MSM block" zero?
39 FF base_x = 0; // [P] = (base_x, base_y)
40 FF base_y = 0; // [P] = (base_x, base_y)
41 bool base_infinity = false; // is [P] == neutral element?
42 uint256_t z1 = 0; // `scalar` = z1 - \lambda * z2 = z1 + \zeta * z2, where $\zeta$ is a primitive sixth root of
43 // unity and \lambda is -\zeta.
44 uint256_t z2 = 0; // `scalar` = z1 - \lambda * z2 = z1 + \zeta * z2, where $\zeta$ is a primitive sixth root of
45 // unity and \lambda is -\zeta.
46 bool z1_zero = false; // `z1 == 0`
47 bool z2_zero = false; // `z2 == 0`
48 uint32_t opcode = 0; // opcode value, in {0, .., 15}, given by 8 * q_add + 4 * q_mul + 2 * q_eq + q_reset.
49
51 // These fields are populated after converting projective to affine coordinates
53
54 // [A] is the current accumulated point, in affine coordinates, for all EC operations.
55 // this is affected by an `add` op-code, or the end of an MSM, or a reset.
56 FF accumulator_x = 0; // [A] = (`accumulator_x`, `accumulator_y`)
57 FF accumulator_y = 0; // [A] = (`accumulator_x`, `accumulator_y`)
58
59 // the following two are accumulators for the MSM.
60 // the difference between (`msm_output_x`, `msm_output_y`) and (`transcript_msm_intermediate_x`,
61 // `transcript_msm_intermediate_y`) is the OFFSET.
62 FF msm_output_x = 0; // if we are at the end of an MSM, output of MSM + OFFSET = (`msm_output_x`,
63 // `msm_output_y`), else, 0.
64 FF msm_output_y = 0; // if we are at the end of an MSM, output of MSM + OFFSET = (`msm_output_x`,
65 // `msm_output_y`), else 0.
67 0; // if we are at the end of an MSM, output of MSM =
68 // (`transcript_msm_intermediate_x`, `transcript_msm_intermediate_y`), else, 0.
70 0; // if we are at the end of an MSM, output of MSM =
71 // (`transcript_msm_intermediate_x`, `transcript_msm_intermediate_y`), else, 0.
72
74 // Computed during the lambda numerator and denominator computation
79 // Computed after the batch inversion
86 };
87
98 {
99 static constexpr auto offset_generator_base =
100 get_precomputed_generators<CycleGroup, "ECCVM_OFFSET_GENERATOR", 1>()[0];
101 static const AffineElement result =
102 AffineElement(Element(offset_generator_base) * grumpkin::fq(uint256_t(1) << 124));
103 return result;
104 }
105 // maintains the state of the VM at any given "time" (i.e., at any given value of pc).
106 struct VMState {
107 uint32_t pc = 0; // decreasing point counter that tracks the total number of multiplications that our virtual
108 // machine has left to compute.
109 uint32_t count = 0; // Number of muls in the current MSM _excluding the current row_.
110 Element accumulator = CycleGroup::affine_point_at_infinity; // accumulator for all group operations.
112 offset_generator(); // accumulator for the current MSM with an offset. (we start with offset_generator,
113 // i.e. random element of the group, to avoid bifurcated logic at each operation step.)
115 };
116
135 static std::vector<TranscriptRow> compute_rows(const std::vector<ECCVMOperation>& vm_operations,
136 const uint32_t total_number_of_muls)
137 {
138 const size_t num_vm_entries = vm_operations.size();
139 // The transcript contains an extra zero row at the beginning and the accumulated state at the end
140 const size_t transcript_size = num_vm_entries + 2;
141 // `transcript_state[i+1]` corresponds to `vm_operation[i]`.
142 std::vector<TranscriptRow> transcript_state(transcript_size);
143
144 // These vectors track quantities that we need to invert.
145 // We fill these vectors and then perform batch inversions to amortize the cost of FF inverts
146 std::vector<FF> inverse_trace_x(num_vm_entries);
147 std::vector<FF> inverse_trace_y(num_vm_entries);
148 std::vector<FF> transcript_msm_x_inverse_trace(num_vm_entries);
149 std::vector<FF> add_lambda_denominator(num_vm_entries);
150 std::vector<FF> add_lambda_numerator(num_vm_entries);
151 std::vector<FF> msm_count_at_transition_inverse_trace(num_vm_entries);
152
153 Accumulator msm_accumulator_trace(num_vm_entries); // ith entry is either neutral element or the value of the
154 // just-completed MSM (shifted by the OFFSET).
155 Accumulator accumulator_trace(num_vm_entries); // ith entry is the total accumulated value up-to-now
156 Accumulator intermediate_accumulator_trace(
157 num_vm_entries); // ith entry is either the neutral element, or the actual value of the just-completed MSM
158 // (i.e., there is no OFFSET).
159
160 VMState state{
161 .pc = total_number_of_muls,
162 .count = 0,
164 .msm_accumulator = offset_generator(),
165 .is_accumulator_empty = true,
166 };
167
168 VMState updated_state;
169
170 // add an empty row: first row is all zeroes because of our shiftable polynomials.
171 transcript_state[0] = (TranscriptRow{});
172
173 // Handle hiding op (index 0) separately before the main loop.
174 // The hiding op has random (non-curve) Px, Py values for ZK purposes - we skip EC computation
175 // and just record the raw field elements. Uses opcode 3 (q_eq=1, q_reset=1).
176 {
177 const ECCVMOperation& hiding_entry = vm_operations[0];
178 TranscriptRow& hiding_row = transcript_state[1];
179
180 hiding_row.base_x = hiding_entry.base_point.x;
181 hiding_row.base_y = hiding_entry.base_point.y;
182 hiding_row.q_eq = hiding_entry.op_code.eq;
183 hiding_row.q_reset_accumulator = hiding_entry.op_code.reset;
184 hiding_row.opcode = hiding_entry.op_code.value();
185 hiding_row.pc = state.pc;
186
187 // Initialize trace arrays to avoid uninitialized values in batch operations
188 accumulator_trace[0] = state.accumulator;
189 msm_accumulator_trace[0] = Element::infinity();
190 intermediate_accumulator_trace[0] = Element::infinity();
191 msm_count_at_transition_inverse_trace[0] = 0;
192 }
193
194 // during the first iteration over the ECCOpQueue, the operations are being performed using Jacobian (a.k.a.
195 // projective) coordinates and the base point coordinates are recorded in the transcript. at the same time, the
196 // transcript logic is being populated (starting from index 1, since index 0 is the hiding op handled above)
197 for (size_t i = 1; i < num_vm_entries; i++) {
198 TranscriptRow& row = transcript_state[i + 1];
199 const ECCVMOperation& entry = vm_operations[i];
200
201 updated_state = state;
202
203 const bool is_mul = entry.op_code.mul;
204 const bool is_add = entry.op_code.add;
205 const bool z1_zero = is_mul ? entry.z1 == 0 : true;
206 const bool z2_zero = is_mul ? entry.z2 == 0 : true;
207
208 const bool base_point_infinity = entry.base_point.is_point_at_infinity();
209 uint32_t num_muls = 0; // number of 128-bit multiplications the vm processes in this op_code.
210 // `num_muls` ∈ {0, 1, 2}.
211 if (is_mul) {
212 num_muls = static_cast<uint32_t>(!z1_zero) + static_cast<uint32_t>(!z2_zero);
213 if (base_point_infinity) {
214 num_muls = 0;
215 }
216 }
217 updated_state.pc = state.pc - num_muls;
218 // if we are at a `reset` or null op, reset the state.
219 // logically, we should add `updated_state.count = 0`, but this is taken care of later by conditional
220 // logic and hence is unnecessary here.
221 if (entry.op_code.reset || entry.op_code.value() == 0) {
222 updated_state.is_accumulator_empty = true;
224 updated_state.msm_accumulator = offset_generator();
225 }
226
227 const bool last_row = (i == (num_vm_entries - 1));
228 // next_not_msm == True if either we are at the last row or the next op_code is *not* a mul.
229 const bool next_not_msm = last_row || !vm_operations[i + 1].op_code.mul;
230
231 // `msm_transition == True` iff we are at the end of an MSM.
232 // this holds iff: current op_code is `mul`, `next_not_msm == True`, and the total number of muls so far in
233 // this MSM (including this op_code) is positive. This later total number
234 // is `state.count + num_muls`.
235 const bool msm_transition = is_mul && next_not_msm && (state.count + num_muls > 0);
236
237 // determine ongoing/continuing msm and update the respective counter
238 const bool current_ongoing_msm = is_mul && !next_not_msm;
239 // we reset the count in updated state if we are not accumulating and not doing an msm
240 updated_state.count = current_ongoing_msm ? state.count + num_muls : 0;
241
242 // process state based on whether we are at a `mul`, then whether or not this is the last mul in an MSM,
243 // then finally if this is an add. note that this mutates `updated_state`. (note that the middle option
244 // depends on the first.)
245 if (is_mul) {
246 process_mul(entry, updated_state, state);
247 }
248
249 if (msm_transition) {
250 process_msm_transition(row, updated_state, state);
251 }
252 // else, these will be set to the neutral element further below.
253 if (is_add) {
254 process_add(entry, updated_state, state);
255 }
256
257 // populate the first group of TranscriptRow entries
258 populate_transcript_row(row, entry, state, num_muls, msm_transition, next_not_msm);
259
260 msm_count_at_transition_inverse_trace[i] = ((state.count + num_muls) == 0) ? 0 : FF(state.count + num_muls);
261
262 // update the accumulators. note that `msm_accumulator_trace` and `intermediate_accumulate_trace` are the
263 // point-at-infinity *unless* we are at the end of an MSM.
264 accumulator_trace[i] = state.accumulator;
265 msm_accumulator_trace[i] = msm_transition ? updated_state.msm_accumulator : Element::infinity();
266 intermediate_accumulator_trace[i] =
267 msm_transition ? (updated_state.msm_accumulator - offset_generator()) : Element::infinity();
268
269 state = updated_state;
270 // if we are the last `mul`in an MSM, set the next state's `msm_accumulator` to the offset.
271 if (is_mul && next_not_msm) {
273 }
274 }
275 // compute affine coordinates of the accumulated points
276 normalize_accumulators(accumulator_trace, msm_accumulator_trace, intermediate_accumulator_trace);
277
278 // add required affine coordinates to the transcript
280 transcript_state, accumulator_trace, msm_accumulator_trace, intermediate_accumulator_trace);
281
282 // process the slopes when adding points or results of MSMs. to increase efficiency, we use batch inversion
283 // after the loop
284 for (size_t i = 0; i < accumulator_trace.size(); ++i) {
285 TranscriptRow& row = transcript_state[i + 1];
286 const bool msm_transition = row.msm_transition;
287
288 const ECCVMOperation& entry = vm_operations[i];
289 const bool is_add = entry.op_code.add;
290
291 if (msm_transition || is_add) {
292 // compute the differences between point coordinates
294 row,
295 intermediate_accumulator_trace[i],
296 transcript_msm_x_inverse_trace[i],
297 msm_accumulator_trace[i],
298 accumulator_trace[i],
299 inverse_trace_x[i],
300 inverse_trace_y[i]);
301
302 // compute the numerators and denominators of slopes between the points
304 entry,
305 intermediate_accumulator_trace[i],
306 accumulator_trace[i],
307 add_lambda_numerator[i],
308 add_lambda_denominator[i]);
309 } else {
312 add_lambda_numerator[i] = 0;
313 add_lambda_denominator[i] = 0;
314 inverse_trace_x[i] = 0;
315 inverse_trace_y[i] = 0;
316 transcript_msm_x_inverse_trace[i] = 0;
317 msm_count_at_transition_inverse_trace[i] = 0;
318 }
319 }
320
321 // Perform all required inversions at once
322 FF::batch_invert(&inverse_trace_x[0], num_vm_entries);
323 FF::batch_invert(&inverse_trace_y[0], num_vm_entries);
324 FF::batch_invert(&transcript_msm_x_inverse_trace[0], num_vm_entries);
325 FF::batch_invert(&add_lambda_denominator[0], num_vm_entries);
326 FF::batch_invert(&msm_count_at_transition_inverse_trace[0], num_vm_entries);
327
328 // Populate the fields of the transcript row containing inverted scalars
329 for (size_t i = 0; i < num_vm_entries; ++i) {
330 TranscriptRow& row = transcript_state[i + 1];
331 row.base_x_inverse = inverse_trace_x[i];
332 row.base_y_inverse = inverse_trace_y[i];
333 row.transcript_msm_x_inverse = transcript_msm_x_inverse_trace[i];
334 row.transcript_add_lambda = add_lambda_numerator[i] * add_lambda_denominator[i];
335 row.msm_count_at_transition_inverse = msm_count_at_transition_inverse_trace[i];
336 }
337
338 // process the final row containing the result of the sequence of group ops in ECCOpQueue
339 finalize_transcript(transcript_state, updated_state);
340
341 return transcript_state;
342 }
343
344 private:
366 const ECCVMOperation& entry,
367 const VMState& state,
368 const uint32_t num_muls,
369 const bool msm_transition,
370 const bool next_not_msm)
371 {
372 const bool base_point_infinity = entry.base_point.is_point_at_infinity();
373
375 row.q_add = entry.op_code.add;
376 row.q_mul = entry.op_code.mul;
377 row.q_eq = entry.op_code.eq;
379 row.msm_transition = msm_transition;
380 row.pc = state.pc;
381 row.msm_count = state.count;
382 row.msm_count_zero_at_transition = ((state.count + num_muls) == 0) && entry.op_code.mul && next_not_msm;
383 row.base_x = ((entry.op_code.add || entry.op_code.mul || entry.op_code.eq) && !base_point_infinity)
384 ? entry.base_point.x
385 : 0;
386 row.base_y = ((entry.op_code.add || entry.op_code.mul || entry.op_code.eq) && !base_point_infinity)
387 ? entry.base_point.y
388 : 0;
389 row.base_infinity =
390 (entry.op_code.add || entry.op_code.mul || entry.op_code.eq) ? (base_point_infinity ? 1 : 0) : 0;
391 row.z1 = entry.op_code.mul ? entry.z1 : 0;
392 row.z2 = entry.op_code.mul ? entry.z2 : 0;
393 row.z1_zero = entry.z1 == 0;
394 row.z2_zero = entry.z2 == 0;
395 row.opcode = entry.op_code.value();
396 }
397
409 static void process_mul(const ECCVMOperation& entry, VMState& updated_state, const VMState& state)
410 {
411 const auto P = typename CycleGroup::element(entry.base_point);
412 const auto R = typename CycleGroup::element(state.msm_accumulator);
413 updated_state.msm_accumulator = R + P * entry.mul_scalar_full;
414 }
415
426 static void process_add(const ECCVMOperation& entry, VMState& updated_state, const VMState& state)
427 {
428
429 if (state.is_accumulator_empty) {
430 updated_state.accumulator = entry.base_point;
431 } else {
432 updated_state.accumulator = Element(state.accumulator) + entry.base_point;
433 }
434 updated_state.is_accumulator_empty = updated_state.accumulator.is_point_at_infinity();
435 }
436
449 static void process_msm_transition(TranscriptRow& row, VMState& updated_state, const VMState& state)
450 {
451 if (state.is_accumulator_empty) {
452 updated_state.accumulator = updated_state.msm_accumulator - offset_generator();
453 } else {
454 const Element R = Element(state.accumulator);
455 updated_state.accumulator = R + updated_state.msm_accumulator - offset_generator();
456 }
457 updated_state.is_accumulator_empty = updated_state.accumulator.is_point_at_infinity();
458
459 const Element msm_output = updated_state.msm_accumulator - offset_generator();
460 row.transcript_msm_infinity = msm_output.is_point_at_infinity();
461 }
470 static void normalize_accumulators(Accumulator& accumulator_trace,
471 Accumulator& msm_accumulator_trace,
472 std::vector<Element>& intermediate_accumulator_trace)
473 {
474 Element::batch_normalize(&accumulator_trace[0], accumulator_trace.size());
475 Element::batch_normalize(&msm_accumulator_trace[0], msm_accumulator_trace.size());
476 Element::batch_normalize(&intermediate_accumulator_trace[0], intermediate_accumulator_trace.size());
477 }
488 const Accumulator& accumulator_trace,
489 const Accumulator& msm_accumulator_trace,
490 const Accumulator& intermediate_accumulator_trace)
491 {
492 for (size_t i = 0; i < accumulator_trace.size(); ++i) {
493 TranscriptRow& row = transcript_state[i + 1];
494 if (!accumulator_trace[i].is_point_at_infinity()) {
495 row.accumulator_x = accumulator_trace[i].x;
496 row.accumulator_y = accumulator_trace[i].y;
497 }
498 if (!msm_accumulator_trace[i].is_point_at_infinity()) {
499 row.msm_output_x = msm_accumulator_trace[i].x;
500 row.msm_output_y = msm_accumulator_trace[i].y;
501 }
502 if (!intermediate_accumulator_trace[i].is_point_at_infinity()) {
503 row.transcript_msm_intermediate_x = intermediate_accumulator_trace[i].x;
504 row.transcript_msm_intermediate_y = intermediate_accumulator_trace[i].y;
505 }
506 }
507 }
526 static void compute_inverse_trace_coordinates(const bool msm_transition,
527 const TranscriptRow& row,
528 const Element& msm_output,
529 FF& transcript_msm_x_inverse_trace,
530 Element& msm_accumulator_trace,
531 Element& accumulator_trace,
532 FF& inverse_trace_x,
533 FF& inverse_trace_y)
534 {
535
536 const bool msm_output_infinity = msm_output.is_point_at_infinity();
537 const bool row_msm_infinity = row.transcript_msm_infinity;
538
539 transcript_msm_x_inverse_trace = (row_msm_infinity || msm_accumulator_trace.is_point_at_infinity())
540 ? 0
541 : (msm_accumulator_trace.x - offset_generator().x);
542
543 FF lhsx;
544 FF lhsy;
545 if (msm_transition) {
546 lhsx = msm_output_infinity ? 0 : msm_output.x;
547 lhsy = msm_output_infinity ? 0 : msm_output.y;
548 } else {
549 lhsx = row.base_x;
550 lhsy = row.base_y;
551 }
552 const FF rhsx = accumulator_trace.is_point_at_infinity() ? 0 : accumulator_trace.x;
553 const FF rhsy = accumulator_trace.is_point_at_infinity() ? 0 : accumulator_trace.y;
554 inverse_trace_x = lhsx - rhsx;
555 inverse_trace_y = lhsy - rhsy;
556 }
557
593 const ECCVMOperation& entry,
594 const Element& intermediate_accumulator,
595 const Element& accumulator,
596 FF& add_lambda_numerator,
597 FF& add_lambda_denominator)
598 {
599 const Element vm_point = entry.op_code.add ? Element(entry.base_point) : intermediate_accumulator;
600
601 const bool vm_infinity = vm_point.is_point_at_infinity();
602 const bool accumulator_infinity = accumulator.is_point_at_infinity();
603
604 // extract coordinates of the current point in ECCOpQueue
605 const FF vm_x = vm_infinity ? 0 : vm_point.x;
606 const FF vm_y = vm_infinity ? 0 : vm_point.y;
607
608 // extract coordinates of the current accumulator
609 const FF accumulator_x = accumulator_infinity ? 0 : accumulator.x;
610 const FF accumulator_y = accumulator_infinity ? 0 : accumulator.y;
611
612 row.transcript_add_x_equal = (vm_x == accumulator_x) || (vm_infinity && accumulator_infinity);
613 row.transcript_add_y_equal = (vm_y == accumulator_y) || (vm_infinity && accumulator_infinity);
614
615 // compute the numerator and denominator of the slope
616 if ((accumulator_x == vm_x) && (accumulator_y == vm_y) && !vm_infinity && !accumulator_infinity) {
617 // Double case (x1 == x2, y1 == y2)
618 add_lambda_denominator = vm_y + vm_y;
619 add_lambda_numerator = vm_x * vm_x * 3;
620 } else if ((accumulator_x != vm_x) && !vm_infinity && !accumulator_infinity) {
621 // General case (x1 != x2)
622 add_lambda_denominator = accumulator_x - vm_x;
623 add_lambda_numerator = accumulator_y - vm_y;
624 }
625 }
633 static void finalize_transcript(std::vector<TranscriptRow>& transcript_state, const VMState& updated_state)
634 {
635 TranscriptRow& final_row = transcript_state.back();
636 final_row.pc = updated_state.pc;
637 final_row.accumulator_x =
638 updated_state.accumulator.is_point_at_infinity() ? 0 : AffineElement(updated_state.accumulator).x;
639 final_row.accumulator_y =
640 updated_state.accumulator.is_point_at_infinity() ? 0 : AffineElement(updated_state.accumulator).y;
641 final_row.accumulator_not_empty = !updated_state.is_accumulator_empty;
642 }
643};
644} // namespace bb
static void add_affine_coordinates_to_transcript(std::vector< TranscriptRow > &transcript_state, const Accumulator &accumulator_trace, const Accumulator &msm_accumulator_trace, const Accumulator &intermediate_accumulator_trace)
Once the point coordinates are converted from Jacobian to affine coordinates, we populate -coordinate...
static void populate_transcript_row(TranscriptRow &row, const ECCVMOperation &entry, const VMState &state, const uint32_t num_muls, const bool msm_transition, const bool next_not_msm)
Populate the transcript rows with the information parsed after the first iteration over the ECCOpQueu...
static void process_msm_transition(TranscriptRow &row, VMState &updated_state, const VMState &state)
typename std::vector< Element > Accumulator
static void finalize_transcript(std::vector< TranscriptRow > &transcript_state, const VMState &updated_state)
Place the number of the MSMs and the coordinates of the accumualted result in the last row of the tra...
static void process_add(const ECCVMOperation &entry, VMState &updated_state, const VMState &state)
Process addition from the ECCOpQueue.
static std::vector< TranscriptRow > compute_rows(const std::vector< ECCVMOperation > &vm_operations, const uint32_t total_number_of_muls)
Computes the ECCVM transcript rows.
static void compute_inverse_trace_coordinates(const bool msm_transition, const TranscriptRow &row, const Element &msm_output, FF &transcript_msm_x_inverse_trace, Element &msm_accumulator_trace, Element &accumulator_trace, FF &inverse_trace_x, FF &inverse_trace_y)
Compute the difference between the x and y coordinates of two points.
static void normalize_accumulators(Accumulator &accumulator_trace, Accumulator &msm_accumulator_trace, std::vector< Element > &intermediate_accumulator_trace)
Batched conversion of points in accumulators from Jacobian coordinates to affine coordinates .
typename CycleGroup::element Element
static void process_mul(const ECCVMOperation &entry, VMState &updated_state, const VMState &state)
Process scalar multiplication from the ECCOpQueue.
typename CycleGroup::affine_element AffineElement
static AffineElement offset_generator()
Computes offset_generator group element.
static void compute_lambda_numerator_and_denominator(TranscriptRow &row, const ECCVMOperation &entry, const Element &intermediate_accumulator, const Element &accumulator, FF &add_lambda_numerator, FF &add_lambda_denominator)
Compute the slope between the VM entry point and the current accumulator for a point addition or doub...
group class. Represents an elliptic curve group element. Group is parametrised by Fq and Fr
Definition group.hpp:38
group_elements::affine_element< Fq, Fr, Params > affine_element
Definition group.hpp:44
static constexpr element point_at_infinity
Definition group.hpp:49
group_elements::element< Fq, Fr, Params > element
Definition group.hpp:43
static constexpr affine_element affine_point_at_infinity
Definition group.hpp:51
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
group< fq, fr, Bn254G1Params > g1
Definition g1.hpp:34
constexpr std::span< const typename Group::affine_element > get_precomputed_generators()
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
AffineElement base_point
uint32_t value() const
static void batch_invert(C &coeffs) noexcept
Batch invert a collection of field elements using Montgomery's trick.