Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
ecc_ops_table.hpp
Go to the documentation of this file.
1// === AUDIT STATUS ===
2// internal: { status: Complete, auditors: [Raju], commit: }
3// external_1: { status: not started, auditors: [], commit: }
4// external_2: { status: not started, auditors: [], commit: }
5// =====================
6
7#pragma once
8
15#include <vector>
16namespace bb {
17
18// Constants determining the structure of the zk columns. These must match the structure expected by Translator.
19static constexpr size_t ECC_NUM_RANDOM_OPS_START = 3;
20static constexpr size_t ECC_NUM_NO_OPS_START = 1;
21static constexpr size_t ECC_NUM_HIDING_OPS_START = 1;
22
37struct EccOpCode {
39 bool add = false;
40 bool mul = false;
41 bool eq = false;
42 bool reset = false;
43 bool operator==(const EccOpCode& other) const = default;
44
45 bool is_random_op = false;
48
49 // encodes add*8 + mul*4 + eq*2 + reset*1
50 [[nodiscard]] uint32_t value() const
51 {
52 if (is_random_op) {
53 throw_or_abort("EccOpCode::value() should not be called on a random op");
54 }
55 auto res = static_cast<uint32_t>(add);
56 res += res;
57 res += static_cast<uint32_t>(mul);
58 res += res;
59 res += static_cast<uint32_t>(eq);
60 res += res;
61 res += static_cast<uint32_t>(reset);
62 return res;
63 }
64};
65
66struct UltraOp {
77
78 bool operator==(const UltraOp& other) const = default;
79
86 {
88 return { Fq(0), Fq(0) };
89 }
90 auto x = Fq((uint256_t(x_hi) << 2 * stdlib::NUM_LIMB_BITS_IN_FIELD_SIMULATION) + uint256_t(x_lo));
91 auto y = Fq((uint256_t(y_hi) << 2 * stdlib::NUM_LIMB_BITS_IN_FIELD_SIMULATION) + uint256_t(y_lo));
92
93 return { x, y };
94 }
95};
96
99 using AffineElement = Curve::Group::affine_element;
106 bool operator==(const ECCVMOperation& other) const = default;
107};
108
116template <typename OpFormat> class EccOpsTable {
117 using Subtable = std::vector<OpFormat>;
118 std::vector<Subtable> table;
119 Subtable current_subtable; // used to store the current subtable before it is added to the table
120 public:
121 size_t size() const
122 {
124 "Current subtable should be merged before computing the size of the full table of ecc ops.");
125 size_t total = 0;
126 for (const auto& subtable : table) {
127 total += subtable.size();
128 }
129
130 return total;
131 }
132
133 size_t num_subtables() const { return table.size(); }
134 size_t get_current_subtable_size() const { return current_subtable.size(); }
135
136 auto& get() const { return table; }
137
138 void push(const OpFormat& op) { current_subtable.push_back(op); }
139
141 {
142 BB_ASSERT(current_subtable.empty(), "Cannot create a new subtable until the current subtable has been merged.");
144 }
145
146 // const operator[]. (there is no non-const version.)
147 const OpFormat& operator[](size_t index) const
148 {
150 "Current subtable should be merged before attempting to index into the full table.");
152 // simple linear search to find the correct subtable
153 for (const auto& subtable : table) {
154 if (index < subtable.size()) {
155 return subtable[index]; // found the correct subtable
156 }
157 index -= subtable.size(); // move to the next subtable
158 }
159 BB_ASSERT(
160 false,
161 "Unreachable: something has gone wrong with the subtable sizes, which do not add up to the table size.");
162 // Unreachable
163 return table.front().front();
164 }
165
166 // highly inefficient copy-based reconstruction of the table for use in ECCVM/Translator. Used once at the end of an
167 // IVC.
168 std::vector<OpFormat> get_reconstructed() const
169 {
171 "current subtable should be merged before reconstructing the full table of operations.");
172
173 std::vector<OpFormat> reconstructed_table;
174 reconstructed_table.reserve(size());
175 for (const auto& subtable : table) {
176 for (const auto& op : subtable) {
177 reconstructed_table.push_back(op);
178 }
179 }
180 return reconstructed_table;
181 }
182
183 void merge()
184 {
185 table.push_back(std::move(current_subtable));
186 current_subtable.clear(); // clear the current subtable after merging
187 BB_ASSERT(current_subtable.empty(), "current subtable should be empty after merging. Check the merge logic.");
188 }
189};
190
196
211 public:
212 static constexpr size_t TABLE_WIDTH = NUM_WIRES; // dictated by the number of wires in the Ultra arithmetization
213 static constexpr size_t NUM_ROWS_PER_OP = 2; // A single ECC op is split across two width-4 rows
214 static constexpr size_t ZK_ULTRA_OPS =
215 (ECC_NUM_RANDOM_OPS_START + ECC_NUM_NO_OPS_START + ECC_NUM_HIDING_OPS_START) * NUM_ROWS_PER_OP;
216
217 // Leading-zero preamble on the APPEND subtable. Matches the appender flavor's TRACE_OFFSET, i.e. the
218 // number of leading zeros carried by its ecc_op_wire polynomial commitments. Sourced from
219 // NUM_DISABLED_ROWS_IN_SUMCHECK, which is == MegaZKFlavor::TRACE_OFFSET.
220 // Must be a multiple of NUM_ROWS_PER_OP so ops land on even row boundaries.
221 static constexpr size_t APPEND_TRACE_OFFSET = NUM_DISABLED_ROWS_IN_SUMCHECK;
222 static_assert(APPEND_TRACE_OFFSET % NUM_ROWS_PER_OP == 0);
223
231 static constexpr size_t compute_fixed_append_offset(size_t append_offset, bool include_zk_prefix = true)
232 {
233 return (include_zk_prefix ? ZK_ULTRA_OPS : 0) + (append_offset * NUM_ROWS_PER_OP);
234 }
235
245 const curve::BN254::BaseField& Py)
246 {
248 using Point = curve::BN254::AffineElement;
249
250 EccOpCode op_code{ .eq = true, .reset = true };
251 Point base_point;
252 base_point.x = Px;
253 base_point.y = Py;
254
255 constexpr size_t CHUNK_SIZE = 2 * stdlib::NUM_LIMB_BITS_IN_FIELD_SIMULATION;
256 uint256_t x_256(Px);
257 uint256_t y_256(Py);
258 UltraOp ultra_op{
259 .op_code = op_code,
260 .x_lo = Fr(x_256.slice(0, CHUNK_SIZE)),
261 .x_hi = Fr(x_256.slice(CHUNK_SIZE, CHUNK_SIZE * 2)),
262 .y_lo = Fr(y_256.slice(0, CHUNK_SIZE)),
263 .y_hi = Fr(y_256.slice(CHUNK_SIZE, CHUNK_SIZE * 2)),
264 .z_1 = Fr(0),
265 .z_2 = Fr(0),
266 .return_is_infinity = false,
267 };
268 ECCVMOperation eccvm_op{ .op_code = op_code, .base_point = base_point };
269 return { ultra_op, eccvm_op };
270 }
271
272 private:
277
278 // Row at which the appended subtable's operations begin: the polynomial start (compute_fixed_append_offset) plus
279 // the APPEND_TRACE_OFFSET leading-zero preamble that the subtable polynomial carries.
280 static constexpr size_t compute_fixed_append_ops_row(size_t append_offset, bool include_zk_prefix)
281 {
282 return compute_fixed_append_offset(append_offset, include_zk_prefix) + APPEND_TRACE_OFFSET;
283 }
284
286 std::vector<UltraOp> zk_ops; // ops used to mask real ops in Chonk
287
288 // Set by merge_with_fixed_append_offset to record the row offset (in NUM_ROWS_PER_OP units) at which the
289 // most recent subtable should be placed when constructing the full table polynomials. Setting this value
290 // also ensures that subsequent reconstructions/polynomial constructions include the APPEND_TRACE_OFFSET
291 // leading-zero preamble for the appended subtable, so the resulting commitments line up with the
292 // appender flavor's ecc_op_wire commitments. See chonk/README.md "Constant Merged Table Size for ZK".
294
295 public:
296 // Returns the number of ECC operations in the table
297 size_t num_ops() const { return table.size(); }
298
299 // Returns the number of rows in the Ultra execution trace (each op occupies NUM_ROWS_PER_OP rows).
300 // NOTE: this count covers the merged subtables only and EXCLUDES the ZK prefix (zk_ops, size ZK_ULTRA_OPS).
301 // Callers that need the full polynomial size (e.g. for sizing a commitment key) must add ZK_ULTRA_OPS.
302 size_t num_ultra_rows() const
303 {
305 return table.size() * NUM_ROWS_PER_OP;
306 }
307 BB_ASSERT(!table.get().empty(), "Fixed-append set but no subtables present");
308 // Last subtable starts at fixed_append_offset (in op units), preceded by APPEND_TRACE_OFFSET zero rows.
309 // This count excludes the ZK prefix, hence include_zk_prefix=false.
310 const size_t last_subtable_rows = table.get().back().size() * NUM_ROWS_PER_OP;
311 return compute_fixed_append_ops_row(fixed_append_offset.value(), /*include_zk_prefix=*/false) +
312 last_subtable_rows;
313 }
315 {
318 0UL,
319 "Current subtable should be merged before computing the size of table of operations up to the tail.");
320 BB_ASSERT_GT(table.num_subtables(), 1UL, "Cannot compute tail table size without at least two tables.");
321 size_t size = 0;
322 for (size_t subtable_idx = 0; subtable_idx < table.num_subtables() - 1; ++subtable_idx) {
323 size += table.get()[subtable_idx].size() * NUM_ROWS_PER_OP;
324 }
325 return size;
326 }
327 void create_new_subtable(size_t size_hint = 0) { table.create_new_subtable(size_hint); }
328 void push(const UltraOp& op) { table.push(op); }
329 bool has_fixed_append_offset() const { return fixed_append_offset.has_value(); }
330 bool has_zk_ops() const { return !zk_ops.empty(); }
331 void merge()
332 {
333 BB_ASSERT(!has_fixed_append_offset(), "Cannot perform regular merge after fixed-location append");
334 table.merge();
335 }
337 {
338 BB_ASSERT(!has_fixed_append_offset(), "Can only perform fixed-location append once");
339
340 size_t prior_subtables_size = 0;
341 for (const auto& subtable : table.get()) {
342 prior_subtables_size += subtable.size();
343 }
344 BB_ASSERT_LTE(prior_subtables_size,
345 offset,
346 "Merged table size exceeds fixed append offset. This means that there are too many ops before "
347 "the last subtable. The last subtable doesn't fit at the end of the op queue.");
348
350 table.merge();
351 }
352
354
355 std::vector<UltraOp> get_no_zk_reconstructed_ultra_ops() const
356 {
357 return get_reconstructed(/*include_zk_ops=*/false);
358 }
359
360 std::vector<UltraOp> get_zk_reconstructed_ultra_ops() const { return get_reconstructed(/*include_zk_ops=*/true); }
361
362 private:
363 // Reconstruct the full table of ultra ops in contiguous memory. When include_zk_ops is set, the result includes
364 // the ZK prefix at the front. Under fixed-location append, the result then has gap no-ops up to the fixed offset,
365 // the APPEND_TRACE_OFFSET zero preamble, then the most recently merged subtable.
366 std::vector<UltraOp> get_reconstructed(const bool include_zk_ops) const
367 {
369 0UL,
370 "current subtable should be merged before reconstructing the full table of operations.");
371 BB_ASSERT(!include_zk_ops || has_zk_ops(), "ZK ops must be constructed before reconstructing the Ultra table.");
372
373 std::vector<UltraOp> reconstructed_table;
374 reconstructed_table.reserve(1 << CONST_OP_QUEUE_LOG_SIZE);
375
376 if (include_zk_ops) {
377 reconstructed_table.insert(reconstructed_table.end(), zk_ops.begin(), zk_ops.end());
378 }
379
381 for (const auto& subtable : table.get()) {
382 reconstructed_table.insert(reconstructed_table.end(), subtable.begin(), subtable.end());
383 }
384 return reconstructed_table;
385 }
386
387 // Previously-merged subtables (everything except the most recent)
388 for (size_t idx = 0; idx + 1 < table.num_subtables(); ++idx) {
389 const auto& subtable = table.get()[idx];
390 reconstructed_table.insert(reconstructed_table.end(), subtable.begin(), subtable.end());
391 }
392
393 // Pad with no-ops up to fixed offset + APPEND_TRACE_OFFSET preamble
394 constexpr size_t preamble_op_slots = APPEND_TRACE_OFFSET / NUM_ROWS_PER_OP;
395 const size_t zk_offset_ops = include_zk_ops ? zk_ops.size() : 0;
396 const size_t target_op_count = fixed_append_offset.value() + zk_offset_ops + preamble_op_slots;
398 reconstructed_table.size(), target_op_count, "Current table size is larger than fixed append offset.");
399 reconstructed_table.insert(
400 reconstructed_table.end(), target_op_count - reconstructed_table.size(), UltraOp{ /* no-op */ });
401
402 // Final subtable
403 const auto& final_subtable = table.get().back();
404 reconstructed_table.insert(reconstructed_table.end(), final_subtable.begin(), final_subtable.end());
405 return reconstructed_table;
406 }
407
408 public:
410 {
411 BB_ASSERT(!has_zk_ops(), "ZK ops should only be constructed once.");
412
413 // Construct the table of ops
414 for (size_t idx = 0; idx < ECC_NUM_NO_OPS_START; idx++) {
415 zk_ops.push_back(UltraOp{ /* no_op */ });
416 }
417
418 // Each random op contributes 8 fresh Fr values to the column polynomials, masking commitments and
419 // evaluations of the columns in the merge protocol and Translator.
420 for (size_t idx = 0; idx < ECC_NUM_RANDOM_OPS_START; idx++) {
421 zk_ops.push_back(UltraOp{ .op_code = EccOpCode{ .is_random_op = true,
422 .random_value_1 = Fr::random_element(),
423 .random_value_2 = Fr::random_element() },
424 .x_lo = Fr::random_element(),
425 .x_hi = Fr::random_element(),
426 .y_lo = Fr::random_element(),
427 .y_hi = Fr::random_element(),
428 .z_1 = Fr::random_element(),
429 .z_2 = Fr::random_element(),
430 .return_is_infinity = false });
431 }
432
434 auto [hiding_ultra_op, hiding_eccvm_op] = make_hiding_op_pair(Fq::random_element(), Fq::random_element());
435 zk_ops.push_back(hiding_ultra_op);
436
437 const size_t poly_size = (zk_ops.size() * NUM_ROWS_PER_OP);
438 BB_ASSERT_EQ(poly_size, ZK_ULTRA_OPS);
439
440 // Construct the column polynomials
441 ColumnPolynomials column_polynomials;
442 for (auto& poly : column_polynomials) {
443 poly = Polynomial<Fr>(poly_size);
444 }
445
446 size_t i = 0;
447 for (const auto& op : zk_ops) {
448 write_op_to_polynomials(column_polynomials, op, i);
449 i += NUM_ROWS_PER_OP;
450 }
451
452 return { column_polynomials, hiding_eccvm_op };
453 }
454
455 // Construct column polynomials for all subtables
457 {
458 std::vector<ColumnPolynomials> subtable_columns;
459
460 for (size_t idx = 0; idx < table.num_subtables(); idx++) {
461 const auto& subtable = table.get()[idx];
462 const size_t poly_size = (subtable.size() * NUM_ROWS_PER_OP);
463 ColumnPolynomials columns = construct_columns_in_range(poly_size, idx, idx + 1);
464 subtable_columns.push_back(std::move(columns));
465 }
466
467 return subtable_columns;
468 }
469
470 // Construct column polynomials for the full ultra ecc ops table
471 ColumnPolynomials construct_table_columns(const bool include_zk_ops = true) const
472 {
473 BB_ASSERT(!include_zk_ops || has_zk_ops(),
474 "ZK ops must be constructed before constructing the full Ultra table with ZK ops.");
476 num_ultra_rows(), 0, table.num_subtables(), include_zk_ops, fixed_append_offset);
477 }
478
479 // Construct column polynomials for the aggregate table up to and including the tail subtable.
481 {
482 BB_ASSERT(has_zk_ops(), "ZK ops should have been constructed before constructing the table up to tail");
484 1UL,
485 "There should be at least two subtables (including the tail) to construct the table up to tail");
486 BB_ASSERT_GT(table.num_subtables(), 0UL, "Cannot construct table up to tail without a current subtable");
487
489 ultra_table_size_up_to_tail(), 0, table.num_subtables() - 1, /*include_zk_ops=*/true);
490 }
491
492 // Construct the columns of the most recently merged subtable.
493 // Under fixed-location append, the returned polynomials carry APPEND_TRACE_OFFSET leading zero rows so their
494 // commitments match the appender's ecc_op_wire commitments.
496 {
497 BB_ASSERT(table.num_subtables() > 0, "Cannot construct current subtable columns with no merged subtables");
498 const size_t leading_zeros = has_fixed_append_offset() ? APPEND_TRACE_OFFSET : 0;
499 const auto& subtable = table.get().back();
500 const size_t poly_size = leading_zeros + (subtable.size() * NUM_ROWS_PER_OP);
501
502 ColumnPolynomials column_polynomials;
503 if (poly_size == 0) {
504 return column_polynomials;
505 }
506 for (auto& poly : column_polynomials) {
507 poly = Polynomial<Fr>(poly_size);
508 }
509
510 size_t row = leading_zeros;
511 for (const auto& op : subtable) {
512 write_op_to_polynomials(column_polynomials, op, row);
513 row += NUM_ROWS_PER_OP;
514 }
515 return column_polynomials;
516 }
517
518 private:
526 static void write_op_to_polynomials(ColumnPolynomials& column_polynomials, const UltraOp& op, const size_t row_idx)
527 {
528 column_polynomials[0].at(row_idx) = !op.op_code.is_random_op ? op.op_code.value() : op.op_code.random_value_1;
529 column_polynomials[1].at(row_idx) = op.x_lo;
530 column_polynomials[2].at(row_idx) = op.x_hi;
531 column_polynomials[3].at(row_idx) = op.y_lo;
532 column_polynomials[0].at(row_idx + 1) = !op.op_code.is_random_op ? 0 : op.op_code.random_value_2;
533 column_polynomials[1].at(row_idx + 1) = op.y_hi;
534 column_polynomials[2].at(row_idx + 1) = op.z_1;
535 column_polynomials[3].at(row_idx + 1) = op.z_2;
536 }
537
551 const size_t poly_size,
552 const size_t subtable_start_idx,
553 const size_t subtable_end_idx,
554 const bool include_zk_ops = false,
555 const std::optional<size_t> fixed_append_offset_for_last = std::nullopt) const
556 {
557 const size_t final_poly_size = poly_size + (include_zk_ops ? ZK_ULTRA_OPS : 0);
558
559 ColumnPolynomials column_polynomials;
560 if (final_poly_size == 0) {
561 return column_polynomials;
562 }
563 for (auto& poly : column_polynomials) {
564 poly = Polynomial<Fr>(final_poly_size);
565 }
566
567 size_t row = 0;
568
569 if (include_zk_ops) {
570 BB_ASSERT(has_zk_ops(), "ZK ops should have been constructed before including them in the columns");
571 for (const auto& op : zk_ops) {
572 write_op_to_polynomials(column_polynomials, op, row);
573 row += NUM_ROWS_PER_OP;
574 }
575 }
576
577 // Lay out subtables sequentially. If a fixed-append target is set, exclude the last-in-range subtable
578 // from the sequential pass; it is placed at the fixed offset below.
579 const size_t sequential_end =
580 fixed_append_offset_for_last.has_value() ? subtable_end_idx - 1 : subtable_end_idx;
581 for (size_t idx = subtable_start_idx; idx < sequential_end; ++idx) {
582 for (const auto& op : table.get()[idx]) {
583 write_op_to_polynomials(column_polynomials, op, row);
584 row += NUM_ROWS_PER_OP;
585 }
586 }
587
588 if (fixed_append_offset_for_last.has_value()) {
589 // The appended subtable's operations begin after the optional ZK prefix and the APPEND_TRACE_OFFSET
590 // leading-zero preamble.
591 size_t append_row = compute_fixed_append_ops_row(fixed_append_offset_for_last.value(), include_zk_ops);
592 for (const auto& op : table.get()[subtable_end_idx - 1]) {
593 write_op_to_polynomials(column_polynomials, op, append_row);
594 append_row += NUM_ROWS_PER_OP;
595 }
596 }
597
598 return column_polynomials;
599 }
600};
601
602} // namespace bb
#define BB_ASSERT(expression,...)
Definition assert.hpp:70
#define BB_ASSERT_GT(left, right,...)
Definition assert.hpp:113
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:83
#define BB_ASSERT_LTE(left, right,...)
Definition assert.hpp:158
#define BB_ASSERT_LT(left, right,...)
Definition assert.hpp:143
A table of ECC operations.
std::vector< OpFormat > Subtable
std::vector< Subtable > table
size_t size() const
size_t get_current_subtable_size() const
void create_new_subtable(size_t size_hint=0)
auto & get() const
Subtable current_subtable
size_t num_subtables() const
std::vector< OpFormat > get_reconstructed() const
const OpFormat & operator[](size_t index) const
void push(const OpFormat &op)
Stores a table of elliptic curve operations represented in the Ultra format.
std::pair< ColumnPolynomials, ECCVMOperation > construct_zk_columns()
Curve::ScalarField Fr
size_t get_current_subtable_size() const
std::vector< UltraOp > zk_ops
std::optional< size_t > fixed_append_offset
size_t ultra_table_size_up_to_tail() const
std::vector< ColumnPolynomials > construct_subtable_columns() const
void push(const UltraOp &op)
size_t num_ultra_rows() const
static constexpr size_t compute_fixed_append_offset(size_t append_offset, bool include_zk_prefix=true)
Shift size of the APPEND merge: the start row of the appended subtable's polynomial in the merged tab...
ColumnPolynomials construct_current_ultra_ops_subtable_columns() const
static constexpr size_t compute_fixed_append_ops_row(size_t append_offset, bool include_zk_prefix)
static constexpr size_t APPEND_TRACE_OFFSET
ColumnPolynomials construct_table_columns_up_to_tail() const
std::array< Polynomial< Fr >, TABLE_WIDTH > ColumnPolynomials
std::vector< UltraOp > get_reconstructed(const bool include_zk_ops) const
static constexpr size_t NUM_ROWS_PER_OP
ColumnPolynomials construct_columns_in_range(const size_t poly_size, const size_t subtable_start_idx, const size_t subtable_end_idx, const bool include_zk_ops=false, const std::optional< size_t > fixed_append_offset_for_last=std::nullopt) const
Construct column polynomials covering subtables [start, end), optionally with a ZK prefix and an opti...
void create_new_subtable(size_t size_hint=0)
static std::pair< UltraOp, ECCVMOperation > make_hiding_op_pair(const curve::BN254::BaseField &Px, const curve::BN254::BaseField &Py)
Build a hiding op as paired Ultra and ECCVM operations from raw Fq coordinates.
std::vector< UltraOp > get_no_zk_reconstructed_ultra_ops() const
bool has_fixed_append_offset() const
static void write_op_to_polynomials(ColumnPolynomials &column_polynomials, const UltraOp &op, const size_t row_idx)
Write a single UltraOp to the column polynomials at the given position.
static constexpr size_t TABLE_WIDTH
ColumnPolynomials construct_table_columns(const bool include_zk_ops=true) const
static constexpr size_t ZK_ULTRA_OPS
void merge_with_fixed_append_offset(size_t offset)
std::vector< UltraOp > get_zk_reconstructed_ultra_ops() const
bb::fq BaseField
Definition bn254.hpp:19
typename Group::affine_element AffineElement
Definition bn254.hpp:22
bb::fr ScalarField
Definition bn254.hpp:18
constexpr uint256_t slice(uint64_t start, uint64_t end) const
ssize_t offset
Definition engine.cpp:62
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
AffineElement base_point
Curve::Group::affine_element AffineElement
bool operator==(const ECCVMOperation &other) const =default
Defines the opcodes for ECC operations used in both the Ultra and ECCVM formats. There are three opco...
bool operator==(const EccOpCode &other) const =default
uint32_t value() const
curve::BN254::ScalarField Fr
bool return_is_infinity
EccOpCode op_code
bool operator==(const UltraOp &other) const =default
std::array< Fq, 2 > get_base_point_standard_form() const
Get the point in standard form i.e. as two coordinates x and y in the base field or as a point at inf...
curve::BN254::BaseField Fq
static field random_element(numeric::RNG *engine=nullptr) noexcept
void throw_or_abort(std::string const &err)