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: not started, auditors: [], date: YYYY-MM-DD }
3// external_1: { status: not started, auditors: [], date: YYYY-MM-DD }
4// external_2: { status: not started, auditors: [], date: YYYY-MM-DD }
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 }
106 {
107 return AffineElement(Element(other) - offset_generator());
108 }
109
110 // maintains the state of the VM at any given "time" (i.e., at any given value of pc).
111 struct VMState {
112 uint32_t pc = 0; // decreasing point counter that tracks the total number of multiplications that our virtual
113 // machine has left to compute.
114 uint32_t count = 0; // Number of muls in the current MSM _excluding the current row_.
115 Element accumulator = CycleGroup::affine_point_at_infinity; // accumulator for all group operations.
117 offset_generator(); // accumulator for the current MSM with an offset. (we start with offset_generator,
118 // i.e. random element of the group, to avoid bifurcated logic at each operation step.)
120 };
121
140 static std::vector<TranscriptRow> compute_rows(const std::vector<ECCVMOperation>& vm_operations,
141 const uint32_t total_number_of_muls)
142 {
143 const size_t num_vm_entries = vm_operations.size();
144 // The transcript contains an extra zero row at the beginning and the accumulated state at the end
145 const size_t transcript_size = num_vm_entries + 2;
146 // `transcript_state[i+1]` corresponds to `vm_operation[i]`.
147 std::vector<TranscriptRow> transcript_state(transcript_size);
148
149 // These vectors track quantities that we need to invert.
150 // We fill these vectors and then perform batch inversions to amortize the cost of FF inverts
151 std::vector<FF> inverse_trace_x(num_vm_entries);
152 std::vector<FF> inverse_trace_y(num_vm_entries);
153 std::vector<FF> transcript_msm_x_inverse_trace(num_vm_entries);
154 std::vector<FF> add_lambda_denominator(num_vm_entries);
155 std::vector<FF> add_lambda_numerator(num_vm_entries);
156 std::vector<FF> msm_count_at_transition_inverse_trace(num_vm_entries);
157
158 Accumulator msm_accumulator_trace(num_vm_entries); // ith entry is either neutral element or the value of the
159 // just-completed MSM (shifted by the OFFSET).
160 Accumulator accumulator_trace(num_vm_entries); // ith entry is the total accumulated value up-to-now
161 Accumulator intermediate_accumulator_trace(
162 num_vm_entries); // ith entry is either the neutral element, or the actual value of the just-completed MSM
163 // (i.e., there is no OFFSET).
164
165 VMState state{
166 .pc = total_number_of_muls,
167 .count = 0,
169 .msm_accumulator = offset_generator(),
170 .is_accumulator_empty = true,
171 };
172
173 VMState updated_state;
174
175 // add an empty row: first row is all zeroes because of our shiftable polynomials.
176 transcript_state[0] = (TranscriptRow{});
177
178 // Handle hiding op (index 0) separately before the main loop.
179 // The hiding op has random (non-curve) Px, Py values for ZK purposes - we skip EC computation
180 // and just record the raw field elements. Uses opcode 3 (q_eq=1, q_reset=1).
181 {
182 const ECCVMOperation& hiding_entry = vm_operations[0];
183 TranscriptRow& hiding_row = transcript_state[1];
184
185 hiding_row.base_x = hiding_entry.base_point.x;
186 hiding_row.base_y = hiding_entry.base_point.y;
187 hiding_row.q_eq = hiding_entry.op_code.eq;
188 hiding_row.q_reset_accumulator = hiding_entry.op_code.reset;
189 hiding_row.opcode = hiding_entry.op_code.value();
190 hiding_row.pc = state.pc;
191
192 // Initialize trace arrays to avoid uninitialized values in batch operations
193 accumulator_trace[0] = state.accumulator;
194 msm_accumulator_trace[0] = Element::infinity();
195 intermediate_accumulator_trace[0] = Element::infinity();
196 msm_count_at_transition_inverse_trace[0] = 0;
197 }
198
199 // during the first iteration over the ECCOpQueue, the operations are being performed using Jacobian (a.k.a.
200 // projective) coordinates and the base point coordinates are recorded in the transcript. at the same time, the
201 // transcript logic is being populated (starting from index 1, since index 0 is the hiding op handled above)
202 for (size_t i = 1; i < num_vm_entries; i++) {
203 TranscriptRow& row = transcript_state[i + 1];
204 const ECCVMOperation& entry = vm_operations[i];
205
206 updated_state = state;
207
208 const bool is_mul = entry.op_code.mul;
209 const bool is_add = entry.op_code.add;
210 const bool z1_zero = is_mul ? entry.z1 == 0 : true;
211 const bool z2_zero = is_mul ? entry.z2 == 0 : true;
212
213 const bool base_point_infinity = entry.base_point.is_point_at_infinity();
214 uint32_t num_muls = 0; // number of 128-bit multiplications the vm processes in this op_code.
215 // `num_muls` ∈ {0, 1, 2}.
216 if (is_mul) {
217 num_muls = static_cast<uint32_t>(!z1_zero) + static_cast<uint32_t>(!z2_zero);
218 if (base_point_infinity) {
219 num_muls = 0;
220 }
221 }
222 updated_state.pc = state.pc - num_muls;
223 // if we are at a `reset` or null op, reset the state.
224 // logically, we should add `updated_state.count = 0`, but this is taken care of later by conditional
225 // logic and hence is unnecessary here.
226 if (entry.op_code.reset || entry.op_code.value() == 0) {
227 updated_state.is_accumulator_empty = true;
229 updated_state.msm_accumulator = offset_generator();
230 }
231
232 const bool last_row = (i == (num_vm_entries - 1));
233 // next_not_msm == True if either we are at the last row or the next op_code is *not* a mul.
234 const bool next_not_msm = last_row || !vm_operations[i + 1].op_code.mul;
235
236 // `msm_transition == True` iff we are at the end of an MSM.
237 // this holds iff: current op_code is `mul`, `next_not_msm == True`, and the total number of muls so far in
238 // this MSM (including this op_code) is positive. This later total number
239 // is `state.count + num_muls`.
240 const bool msm_transition = is_mul && next_not_msm && (state.count + num_muls > 0);
241
242 // determine ongoing/continuing msm and update the respective counter
243 const bool current_ongoing_msm = is_mul && !next_not_msm;
244 // we reset the count in updated state if we are not accumulating and not doing an msm
245 updated_state.count = current_ongoing_msm ? state.count + num_muls : 0;
246
247 // process state based on whether we are at a `mul`, then whether or not this is the last mul in an MSM,
248 // then finally if this is an add. note that this mutates `updated_state`. (note that the middle option
249 // depends on the first.)
250 if (is_mul) {
251 process_mul(entry, updated_state, state);
252 }
253
254 if (msm_transition) {
255 process_msm_transition(row, updated_state, state);
256 } else {
257 msm_accumulator_trace[i] = Element::infinity();
258 intermediate_accumulator_trace[i] = Element::infinity();
259 }
260
261 if (is_add) {
262 process_add(entry, updated_state, state);
263 }
264
265 // populate the first group of TranscriptRow entries
266 populate_transcript_row(row, entry, state, num_muls, msm_transition, next_not_msm);
267
268 msm_count_at_transition_inverse_trace[i] = ((state.count + num_muls) == 0) ? 0 : FF(state.count + num_muls);
269
270 // update the accumulators. note that `msm_accumulator_trace` and `intermediate_accumulate_trace` are the
271 // point-at-infinity *unless* we are at the end of an MSM.
272 accumulator_trace[i] = state.accumulator;
273 msm_accumulator_trace[i] = msm_transition ? updated_state.msm_accumulator : Element::infinity();
274 intermediate_accumulator_trace[i] =
275 msm_transition ? (updated_state.msm_accumulator - offset_generator()) : Element::infinity();
276
277 state = updated_state;
278 // if we are the last `mul`in an MSM, set the next state's `msm_accumulator` to the offset.
279 if (is_mul && next_not_msm) {
281 }
282 }
283 // compute affine coordinates of the accumulated points
284 normalize_accumulators(accumulator_trace, msm_accumulator_trace, intermediate_accumulator_trace);
285
286 // add required affine coordinates to the transcript
288 transcript_state, accumulator_trace, msm_accumulator_trace, intermediate_accumulator_trace);
289
290 // process the slopes when adding points or results of MSMs. to increase efficiency, we use batch inversion
291 // after the loop
292 for (size_t i = 0; i < accumulator_trace.size(); ++i) {
293 TranscriptRow& row = transcript_state[i + 1];
294 const bool msm_transition = row.msm_transition;
295
296 const ECCVMOperation& entry = vm_operations[i];
297 const bool is_add = entry.op_code.add;
298
299 if (msm_transition || is_add) {
300 // compute the differences between point coordinates
302 row,
303 intermediate_accumulator_trace[i],
304 transcript_msm_x_inverse_trace[i],
305 msm_accumulator_trace[i],
306 accumulator_trace[i],
307 inverse_trace_x[i],
308 inverse_trace_y[i]);
309
310 // compute the numerators and denominators of slopes between the points
312 entry,
313 intermediate_accumulator_trace[i],
314 accumulator_trace[i],
315 add_lambda_numerator[i],
316 add_lambda_denominator[i]);
317 } else {
320 add_lambda_numerator[i] = 0;
321 add_lambda_denominator[i] = 0;
322 inverse_trace_x[i] = 0;
323 inverse_trace_y[i] = 0;
324 }
325 }
326
327 // Perform all required inversions at once
328 FF::batch_invert(&inverse_trace_x[0], num_vm_entries);
329 FF::batch_invert(&inverse_trace_y[0], num_vm_entries);
330 FF::batch_invert(&transcript_msm_x_inverse_trace[0], num_vm_entries);
331 FF::batch_invert(&add_lambda_denominator[0], num_vm_entries);
332 FF::batch_invert(&msm_count_at_transition_inverse_trace[0], num_vm_entries);
333
334 // Populate the fields of the transcript row containing inverted scalars
335 for (size_t i = 0; i < num_vm_entries; ++i) {
336 TranscriptRow& row = transcript_state[i + 1];
337 row.base_x_inverse = inverse_trace_x[i];
338 row.base_y_inverse = inverse_trace_y[i];
339 row.transcript_msm_x_inverse = transcript_msm_x_inverse_trace[i];
340 row.transcript_add_lambda = add_lambda_numerator[i] * add_lambda_denominator[i];
341 row.msm_count_at_transition_inverse = msm_count_at_transition_inverse_trace[i];
342 }
343
344 // process the final row containing the result of the sequence of group ops in ECCOpQueue
345 finalize_transcript(transcript_state, updated_state);
346
347 return transcript_state;
348 }
349
350 private:
372 const ECCVMOperation& entry,
373 const VMState& state,
374 const uint32_t num_muls,
375 const bool msm_transition,
376 const bool next_not_msm)
377 {
378 const bool base_point_infinity = entry.base_point.is_point_at_infinity();
379
381 row.q_add = entry.op_code.add;
382 row.q_mul = entry.op_code.mul;
383 row.q_eq = entry.op_code.eq;
385 row.msm_transition = msm_transition;
386 row.pc = state.pc;
387 row.msm_count = state.count;
388 row.msm_count_zero_at_transition = ((state.count + num_muls) == 0) && entry.op_code.mul && next_not_msm;
389 row.base_x = ((entry.op_code.add || entry.op_code.mul || entry.op_code.eq) && !base_point_infinity)
390 ? entry.base_point.x
391 : 0;
392 row.base_y = ((entry.op_code.add || entry.op_code.mul || entry.op_code.eq) && !base_point_infinity)
393 ? entry.base_point.y
394 : 0;
395 row.base_infinity =
396 (entry.op_code.add || entry.op_code.mul || entry.op_code.eq) ? (base_point_infinity ? 1 : 0) : 0;
397 row.z1 = entry.op_code.mul ? entry.z1 : 0;
398 row.z2 = entry.op_code.mul ? entry.z2 : 0;
399 row.z1_zero = entry.z1 == 0;
400 row.z2_zero = entry.z2 == 0;
401 row.opcode = entry.op_code.value();
402 }
403
415 static void process_mul(const ECCVMOperation& entry, VMState& updated_state, const VMState& state)
416 {
417 const auto P = typename CycleGroup::element(entry.base_point);
418 const auto R = typename CycleGroup::element(state.msm_accumulator);
419 updated_state.msm_accumulator = R + P * entry.mul_scalar_full;
420 }
421
432 static void process_add(const ECCVMOperation& entry, VMState& updated_state, const VMState& state)
433 {
434
435 if (state.is_accumulator_empty) {
436 updated_state.accumulator = entry.base_point;
437 } else {
438 updated_state.accumulator = Element(state.accumulator) + entry.base_point;
439 }
440 updated_state.is_accumulator_empty = updated_state.accumulator.is_point_at_infinity();
441 }
442
455 static void process_msm_transition(TranscriptRow& row, VMState& updated_state, const VMState& state)
456 {
457 if (state.is_accumulator_empty) {
458 updated_state.accumulator = updated_state.msm_accumulator - offset_generator();
459 } else {
460 const Element R = Element(state.accumulator);
461 updated_state.accumulator = R + updated_state.msm_accumulator - offset_generator();
462 }
463 updated_state.is_accumulator_empty = updated_state.accumulator.is_point_at_infinity();
464
465 const Element msm_output = updated_state.msm_accumulator - offset_generator();
466 row.transcript_msm_infinity = msm_output.is_point_at_infinity();
467 }
476 static void normalize_accumulators(Accumulator& accumulator_trace,
477 Accumulator& msm_accumulator_trace,
478 std::vector<Element>& intermediate_accumulator_trace)
479 {
480 Element::batch_normalize(&accumulator_trace[0], accumulator_trace.size());
481 Element::batch_normalize(&msm_accumulator_trace[0], msm_accumulator_trace.size());
482 Element::batch_normalize(&intermediate_accumulator_trace[0], intermediate_accumulator_trace.size());
483 }
494 const Accumulator& accumulator_trace,
495 const Accumulator& msm_accumulator_trace,
496 const Accumulator& intermediate_accumulator_trace)
497 {
498 for (size_t i = 0; i < accumulator_trace.size(); ++i) {
499 TranscriptRow& row = transcript_state[i + 1];
500 if (!accumulator_trace[i].is_point_at_infinity()) {
501 row.accumulator_x = accumulator_trace[i].x;
502 row.accumulator_y = accumulator_trace[i].y;
503 }
504 if (!msm_accumulator_trace[i].is_point_at_infinity()) {
505 row.msm_output_x = msm_accumulator_trace[i].x;
506 row.msm_output_y = msm_accumulator_trace[i].y;
507 }
508 if (!intermediate_accumulator_trace[i].is_point_at_infinity()) {
509 row.transcript_msm_intermediate_x = intermediate_accumulator_trace[i].x;
510 row.transcript_msm_intermediate_y = intermediate_accumulator_trace[i].y;
511 }
512 }
513 }
532 static void compute_inverse_trace_coordinates(const bool msm_transition,
533 const TranscriptRow& row,
534 const Element& msm_output,
535 FF& transcript_msm_x_inverse_trace,
536 Element& msm_accumulator_trace,
537 Element& accumulator_trace,
538 FF& inverse_trace_x,
539 FF& inverse_trace_y)
540 {
541
542 const bool msm_output_infinity = msm_output.is_point_at_infinity();
543 const bool row_msm_infinity = row.transcript_msm_infinity;
544
545 transcript_msm_x_inverse_trace = row_msm_infinity ? 0 : (msm_accumulator_trace.x - offset_generator().x);
546
547 FF lhsx;
548 FF lhsy;
549 if (msm_transition) {
550 lhsx = msm_output_infinity ? 0 : msm_output.x;
551 lhsy = msm_output_infinity ? 0 : msm_output.y;
552 } else {
553 lhsx = row.base_x;
554 lhsy = row.base_y;
555 }
556 const FF rhsx = accumulator_trace.is_point_at_infinity() ? 0 : accumulator_trace.x;
557 const FF rhsy = accumulator_trace.is_point_at_infinity() ? 0 : accumulator_trace.y;
558 inverse_trace_x = lhsx - rhsx;
559 inverse_trace_y = lhsy - rhsy;
560 }
561
596 const ECCVMOperation& entry,
597 const Element& intermediate_accumulator,
598 const Element& accumulator,
599 FF& add_lambda_numerator,
600 FF& add_lambda_denominator)
601 {
602 const Element vm_point = entry.op_code.add ? Element(entry.base_point) : intermediate_accumulator;
603
604 const bool vm_infinity = vm_point.is_point_at_infinity();
605 const bool accumulator_infinity = accumulator.is_point_at_infinity();
606
607 // extract coordinates of the current point in ECCOpQueue
608 const FF vm_x = vm_infinity ? 0 : vm_point.x;
609 const FF vm_y = vm_infinity ? 0 : vm_point.y;
610
611 // extract coordinates of the current accumulator
612 const FF accumulator_x = accumulator_infinity ? 0 : accumulator.x;
613 const FF accumulator_y = accumulator_infinity ? 0 : accumulator.y;
614
615 row.transcript_add_x_equal = (vm_x == accumulator_x) || (vm_infinity && accumulator_infinity);
616 row.transcript_add_y_equal = (vm_y == accumulator_y) || (vm_infinity && accumulator_infinity);
617
618 // compute the numerator and denominator of the slope
619 if ((accumulator_x == vm_x) && (accumulator_y == vm_y) && !vm_infinity && !accumulator_infinity) {
620 // Double case (x1 == x2, y1 == y2)
621 add_lambda_denominator = vm_y + vm_y;
622 add_lambda_numerator = vm_x * vm_x * 3;
623 } else if ((accumulator_x != vm_x) && !vm_infinity && !accumulator_infinity) {
624 // General case (x1 != x2)
625 add_lambda_denominator = accumulator_x - vm_x;
626 add_lambda_numerator = accumulator_y - vm_y;
627 }
628 }
636 static void finalize_transcript(std::vector<TranscriptRow>& transcript_state, const VMState& updated_state)
637 {
638 TranscriptRow& final_row = transcript_state.back();
639 final_row.pc = updated_state.pc;
640 final_row.accumulator_x =
641 updated_state.accumulator.is_point_at_infinity() ? 0 : AffineElement(updated_state.accumulator).x;
642 final_row.accumulator_y =
643 updated_state.accumulator.is_point_at_infinity() ? 0 : AffineElement(updated_state.accumulator).y;
644 final_row.accumulator_not_empty = !updated_state.is_accumulator_empty;
645 }
646};
647} // 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 remove_offset_generator(const AffineElement &other)
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)
If entry is not a point at infinity, compute the slope between the VM entry point and current accumul...
group class. Represents an elliptic curve group element. Group is parametrised by Fq and Fr
Definition group.hpp:36
group_elements::affine_element< Fq, Fr, Params > affine_element
Definition group.hpp:42
static constexpr element point_at_infinity
Definition group.hpp:47
group_elements::element< Fq, Fr, Params > element
Definition group.hpp:41
static constexpr affine_element affine_point_at_infinity
Definition group.hpp:49
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
group< fq, fr, Bn254G1Params > g1
Definition g1.hpp:33
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