Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
block_constraint.test.cpp
Go to the documentation of this file.
2#include "acir_format.hpp"
8
12
13#include <cstdint>
14#include <gtest/gtest.h>
15#include <vector>
16
17using namespace acir_format;
18
19namespace {
21} // namespace
22
26template <AccessType access_type>
27void add_constant_ops(const size_t table_size,
28 const std::vector<bb::fr>& table_values,
29 WitnessVector& witness_values,
30 std::vector<MemOp>& trace)
31{
32 const size_t table_index = static_cast<size_t>(engine.get_random_uint32() % table_size);
33 bb::fr value_fr =
34 access_type == AccessType::Read ? table_values[table_index] : table_values[table_index] + bb::fr(1);
35
36 // Index constant, value witness
37 {
41
42 const MemOp read_op = { .access_type = access_type, .index = index, .value = value };
43
44 trace.push_back(read_op);
45 }
46 // Index witness, value constant
47 {
49 add_to_witness_and_track_indices(witness_values, bb::fr(table_index)));
51
52 const MemOp read_op = { .access_type = access_type, .index = index, .value = value };
53
54 trace.push_back(read_op);
55 }
56 // Index constant, value constant
57 {
60
61 const MemOp read_op = { .access_type = access_type, .index = index, .value = value };
62
63 trace.push_back(read_op);
64 }
65}
66
67template <typename Builder_, size_t TableSize_, size_t NumReads_, bool PerformConstantOps_> struct ROMTestParams {
68 using Builder = Builder_;
69 static constexpr size_t table_size = TableSize_;
70 static constexpr size_t num_reads = NumReads_;
71 static constexpr bool perform_constant_ops = PerformConstantOps_;
72};
73
74template <typename Builder_, size_t table_size, size_t num_reads, bool perform_constant_ops> class ROMTestingFunctions {
75 public:
77 using Builder = Builder_;
79 public:
80 enum class Target : uint8_t { None, ReadValueIncremented };
82 {
84 if constexpr (num_reads > 0 && table_size > 0) {
85 targets.push_back(Target::ReadValueIncremented);
86 }
87 return targets;
88 };
89 static std::vector<std::string> get_labels()
90 {
91 std::vector<std::string> labels = { "None" };
92 if constexpr (num_reads > 0 && table_size > 0) {
93 labels.push_back("ReadValueIncremented");
94 }
95 return labels;
96 };
97 };
98
100
101 static void generate_constraints(AcirConstraint& memory_constraint, WitnessVector& witness_values)
102 {
103 // Create initial memory values "natively"
104 std::vector<bb::fr> table_values;
105 table_values.reserve(table_size);
106 for (size_t _i = 0; _i < table_size; _i++) {
107 table_values.push_back(bb::fr::random_element());
108 }
109
110 // `init_poly` represents the _initial values_ of the circuit.
111 std::vector<uint32_t> init_indices;
112 for (const auto& val : table_values) {
113 uint32_t value_index = add_to_witness_and_track_indices(witness_values, val);
114 // push the circuit incarnation of the value in `init_indices`
115 init_indices.push_back(value_index);
116 }
117
118 // Initialize and create memory operations
120
121 // Add index witness only if we have a non-empty table
122 if constexpr (table_size > 0) {
123 for (size_t _i = 0; _i < num_reads; ++_i) {
124 const size_t rom_index_to_read = static_cast<size_t>(engine.get_random_uint32() % table_size);
125 // Add value witness
126 bb::fr read_value = table_values[rom_index_to_read];
127
129 add_to_witness_and_track_indices(witness_values, bb::fr(rom_index_to_read)));
130 WitnessOrConstant<bb::fr> value_for_read =
132
133 const MemOp read_op = { .access_type = AccessType::Read,
134 .index = index_for_read,
135 .value = value_for_read };
136
137 trace.push_back(read_op);
138 }
139 if constexpr (perform_constant_ops) {
140 add_constant_ops<AccessType::Read>(table_size, table_values, witness_values, trace);
141 }
142 }
143 // Create the MemoryConstraint
144 memory_constraint = AcirConstraint{ .init = init_indices, .trace = trace, .type = BlockType::ROM };
145 }
146
148 [[maybe_unused]] AcirConstraint memory_constraint,
149 WitnessVector witness_values,
150 const InvalidWitness::Target& invalid_witness_target)
151 {
152 switch (invalid_witness_target) {
154 break;
156 if constexpr (num_reads > 0 && table_size > 0) {
157 // Tamper with a random read value
158 const size_t random_read = static_cast<size_t>(engine.get_random_uint32() % num_reads);
159 // Each read has 2 witness values: index at offset 0, value at offset 1
160 // The reads start after the table_size init values
161 const size_t read_value_witness_index = table_size + (random_read * 2) + 1;
162 witness_values[read_value_witness_index] += bb::fr(1);
163 }
164 break;
165 }
166
167 return { memory_constraint, witness_values };
168 }
169};
170template <typename Params>
171class ROMTest : public ::testing::Test,
172 public TestClass<ROMTestingFunctions<typename Params::Builder,
173 Params::table_size,
174 Params::num_reads,
175 Params::perform_constant_ops>> {
176 protected:
178};
179
180using ROMTestConfigs = testing::Types<ROMTestParams<UltraCircuitBuilder, 0, 0, false>,
182 ROMTestParams<UltraCircuitBuilder, 10, 0, true>, // Test the case in which there
183 // are only constant operations
188 ROMTestParams<MegaCircuitBuilder, 10, 0, true>, // Test the case in which there
189 // are only constant operations
193
194TYPED_TEST(ROMTest, GenerateVKFromConstraints)
195{
196 using Flavor =
198 TestFixture::template test_vk_independence<Flavor>();
199}
200
202{
203 TestFixture::test_tampering();
204}
205
206template <typename Builder_, size_t TableSize_, size_t NumReads_, size_t NumWrites_, bool PerformConstantOps_>
208 using Builder = Builder_;
209 static constexpr size_t table_size = TableSize_;
210 static constexpr size_t num_reads = NumReads_;
211 static constexpr size_t num_writes = NumWrites_;
212 static constexpr bool perform_constant_ops = PerformConstantOps_;
213};
214
215template <typename Builder_, size_t table_size, size_t num_reads, size_t num_writes, bool perform_constant_ops>
217 public:
219 using Builder = Builder_;
220
221 // Track witness value and its index in witness_values
225 };
226
228 public:
229 enum class Target : uint8_t { None, ReadValueIncremented };
231 {
232 std::vector<Target> targets = { Target::None };
233 if constexpr (num_reads > 0 && table_size > 0) {
234 targets.push_back(Target::ReadValueIncremented);
235 }
236 return targets;
237 };
238 static std::vector<std::string> get_labels()
239 {
240 std::vector<std::string> labels = { "None" };
241 if constexpr (num_reads > 0 && table_size > 0) {
242 labels.push_back("ReadValueIncremented");
243 }
244 return labels;
245 };
246 };
247
249
250 static void generate_constraints(AcirConstraint& memory_constraint, WitnessVector& witness_values)
251 {
252 // Create initial memory values "natively". RAM tables always start out initialized.
253 std::vector<bb::fr> table_values;
254 table_values.reserve(table_size);
255 for (size_t _i = 0; _i < table_size; _i++) {
256 table_values.push_back(bb::fr::random_element());
257 }
258
259 // `init_indices` contains the initial values of the circuit.
260 std::vector<uint32_t> init_indices;
261 for (size_t i = 0; i < table_size; ++i) {
262 const auto val = table_values[i];
263 uint32_t value_index = add_to_witness_and_track_indices(witness_values, val);
264 init_indices.push_back(value_index);
265 }
266 // Initialize and create memory operations
268 size_t num_reads_remaining = num_reads;
269 size_t num_writes_remaining = num_writes;
270
271 // `read_write_sequence` is a _random_ list of read and write operations.
272 std::vector<AccessType> read_write_sequence;
273 while (num_reads_remaining + num_writes_remaining > 0) {
274 bool try_read = (engine.get_random_uint32() & 1) != 0;
275 if (try_read && (num_reads_remaining > 0)) {
276 read_write_sequence.push_back(AccessType::Read);
277 num_reads_remaining--;
278 } else if (num_writes_remaining > 0) {
279 read_write_sequence.push_back(AccessType::Write);
280 num_writes_remaining--;
281 } else {
282 // writes exhausted, hence only reads left
283 for (size_t _j = 0; _j < num_reads_remaining; _j++) {
284 read_write_sequence.push_back(AccessType::Read);
285 }
286 num_reads_remaining = 0;
287 }
288 }
289
290 // Add read/writes only if we have a non-empty table
291 if constexpr (table_size > 0) {
292 for (auto& access_type : read_write_sequence) {
293 MemOp mem_op;
294 switch (access_type) {
295 case AccessType::Read: {
296 const size_t ram_index_to_read = static_cast<size_t>(engine.get_random_uint32() % table_size);
297 const uint32_t index_for_read =
298 add_to_witness_and_track_indices(witness_values, bb::fr(ram_index_to_read));
299 bb::fr read_value = table_values[ram_index_to_read];
300 const uint32_t value_for_read = add_to_witness_and_track_indices(witness_values, read_value);
301
302 mem_op = { .access_type = AccessType::Read,
303 .index = WitnessOrConstant<bb::fr>::from_index(index_for_read),
304 .value = WitnessOrConstant<bb::fr>::from_index(value_for_read) };
305 trace.push_back(mem_op);
306 break;
307 }
308 case AccessType::Write: {
309 const size_t ram_index_to_write = static_cast<size_t>(engine.get_random_uint32() % table_size);
310 const uint32_t index_to_write =
311 add_to_witness_and_track_indices(witness_values, bb::fr(ram_index_to_write));
312 bb::fr write_value = bb::fr::random_element();
313 const uint32_t value_to_write = add_to_witness_and_track_indices(witness_values, write_value);
314
315 // Update the table_values to reflect this write
316 table_values[ram_index_to_write] = write_value;
317
318 mem_op = { .access_type = AccessType::Write,
319 .index = WitnessOrConstant<bb::fr>::from_index(index_to_write),
320 .value = WitnessOrConstant<bb::fr>::from_index(value_to_write) };
321 trace.push_back(mem_op);
322 break;
323 }
324 }
325 }
326 if constexpr (perform_constant_ops) {
327 add_constant_ops<AccessType::Read>(table_size, table_values, witness_values, trace);
328 add_constant_ops<AccessType::Write>(table_size, table_values, witness_values, trace);
329 }
330 }
331
332 // Create the MemoryConstraint
333 memory_constraint = AcirConstraint{ .init = init_indices, .trace = trace, .type = BlockType::RAM };
334 }
335
337 [[maybe_unused]] AcirConstraint memory_constraint,
338 WitnessVector witness_values,
339 const InvalidWitness::Target& invalid_witness_target)
340 {
341 switch (invalid_witness_target) {
343 break;
345 if constexpr (num_reads > 0 && table_size > 0) {
346 // Tamper with a random read value
347 size_t random_read_idx = static_cast<size_t>(engine.get_random_uint32() % (num_reads + num_writes));
348 while (memory_constraint.trace[random_read_idx].access_type != AccessType::Read) {
349 // Find a read operation
350 random_read_idx = static_cast<size_t>(engine.get_random_uint32() % (num_reads + num_writes));
351 }
352 const uint32_t witness_idx = memory_constraint.trace[random_read_idx].value.index;
353 witness_values[witness_idx] += bb::fr(1);
354 }
355 break;
356 }
357
358 return { memory_constraint, witness_values };
359 }
360};
361
362template <typename Params>
363class RAMTest : public ::testing::Test,
364 public TestClass<RAMTestingFunctions<typename Params::Builder,
365 Params::table_size,
366 Params::num_reads,
367 Params::num_writes,
368 Params::perform_constant_ops>> {
369 protected:
371};
372
373// Failure tests are impossible in the scenario with only writes.
375 testing::Types<RAMTestParams<UltraCircuitBuilder, 0, 0, 0, false>,
377 RAMTestParams<UltraCircuitBuilder, 10, 0, 0, true>, // Test the case in which there are only
378 // constant operations
386 RAMTestParams<MegaCircuitBuilder, 10, 0, 0, true>, // Test the case in which there are only
387 // constant operations
393
395
396TYPED_TEST(RAMTest, GenerateVKFromConstraints)
397{
398 using Flavor =
400 TestFixture::template test_vk_independence<Flavor>();
401}
402
404{
405 TestFixture::test_tampering();
406}
407
408template <CallDataType CallDataType_, size_t CallDataSize_, size_t NumReads_, bool PerformConstantOps_>
410 static constexpr CallDataType calldata_type = CallDataType_;
411 static constexpr size_t calldata_size = CallDataSize_;
412 static constexpr size_t num_reads = NumReads_;
413 static constexpr bool perform_constant_ops = PerformConstantOps_;
414};
415
416template <CallDataType calldata_type, size_t calldata_size, size_t num_reads, bool perform_constant_ops>
418 public:
421
423 public:
424 enum class Target : uint8_t { None, ReadValueIncremented };
425
427 {
428 if constexpr (num_reads > 0) {
430 }
431 return { Target::None };
432 }
433
434 static std::vector<std::string> get_labels()
435 {
436 if constexpr (num_reads > 0) {
437 return { "None", "ReadValueIncremented" };
438 }
439 return { "None" };
440 }
441 };
442
444
445 static void generate_constraints(AcirConstraint& memory_constraint, WitnessVector& witness_values)
446 {
447
448 // Create initial memory values "natively". Memory tables always start out initialized.
449 std::vector<bb::fr> calldata_values;
450 calldata_values.reserve(calldata_size);
451 for (size_t _i = 0; _i < calldata_size; _i++) {
452 calldata_values.push_back(bb::fr::random_element());
453 }
454
455 // `init_indices` contains the initial values of the circuit.
456 std::vector<uint32_t> init_indices;
457 for (size_t i = 0; i < calldata_size; ++i) {
458 uint32_t value_index = add_to_witness_and_track_indices(witness_values, calldata_values[i]);
459 init_indices.push_back(value_index);
460 }
461 // Initialize and create memory operations
463
464 // Add read operations
465 if constexpr (calldata_size > 0) {
466 for (size_t idx = 0; idx < num_reads; ++idx) {
467 MemOp mem_op;
468 const size_t calldata_idx_to_read = static_cast<size_t>(engine.get_random_uint32() % calldata_size);
469 const uint32_t index_for_read =
470 add_to_witness_and_track_indices(witness_values, bb::fr(calldata_idx_to_read));
471 bb::fr read_value = calldata_values[calldata_idx_to_read];
472 const uint32_t value_for_read = add_to_witness_and_track_indices(witness_values, read_value);
473
474 mem_op = { .access_type = AccessType::Read,
475 .index = WitnessOrConstant<bb::fr>::from_index(index_for_read),
476 .value = WitnessOrConstant<bb::fr>::from_index(value_for_read) };
477 trace.push_back(mem_op);
478 }
479 }
480 if constexpr (perform_constant_ops) {
481 add_constant_ops<AccessType::Read>(calldata_size, calldata_values, witness_values, trace);
482 }
483
484 // Create the MemoryConstraint
485 memory_constraint = AcirConstraint{
486 .init = init_indices, .trace = trace, .type = BlockType::CallData, .calldata_id = calldata_type
487 };
488 }
489
491 AcirConstraint memory_constraint,
492 WitnessVector witness_values,
493 const InvalidWitness::Target& invalid_witness_target)
494 {
495 switch (invalid_witness_target) {
497 break;
499 // Tamper with a random read value using the recorded witness index
500 if constexpr (num_reads > 0) {
501 const size_t random_read_idx = static_cast<size_t>(engine.get_random_uint32() % num_reads);
502 const uint32_t witness_idx = memory_constraint.trace[random_read_idx].index.index;
503 witness_values[witness_idx] += bb::fr(1);
504 }
505 break;
506 }
507
508 return { memory_constraint, witness_values };
509 }
510};
511
512using CallDataTestConfigs = testing::Types<CallDataTestParams<CallDataType::Primary, 0, 0, false>,
518
519template <typename Params>
520class CallDataTests : public ::testing::Test,
521 public TestClass<CallDataTestingFunctions<Params::calldata_type,
522 Params::calldata_size,
523 Params::num_reads,
524 Params::perform_constant_ops>> {
525 protected:
527};
528
530
531TYPED_TEST(CallDataTests, GenerateVKFromConstraints)
532{
533 TestFixture::template test_vk_independence<MegaFlavor>();
534}
535
537{
538 TestFixture::test_tampering();
539}
540
541template <size_t returndata_size>
542
544 public:
547
548 // There is no tampering that can be done for ReturnData as the only thing that a return data opcode does is
549 // adding data to the return data bus vector and constraining such data to be equal to the data with which the
550 // memory operation was initialized
552 public:
553 enum class Target : uint8_t { None };
554
555 static std::vector<Target> get_all() { return { Target::None }; };
556
557 static std::vector<std::string> get_labels() { return { "None" }; };
558 };
559
561
562 static void generate_constraints(AcirConstraint& memory_constraint, WitnessVector& witness_values)
563 {
564 // Create initial memory values "natively". Memory tables always start out initialized.
565 std::vector<bb::fr> returndata_values;
566 returndata_values.reserve(returndata_size);
567 for (size_t _i = 0; _i < returndata_size; _i++) {
568 returndata_values.push_back(bb::fr::random_element());
569 }
570
571 // `init_indices` contains the initial values of the circuit.
572 std::vector<uint32_t> init_indices;
573 for (size_t i = 0; i < returndata_size; ++i) {
574 uint32_t value_index = add_to_witness_and_track_indices(witness_values, returndata_values[i]);
575 init_indices.push_back(value_index);
576 }
577
578 // Create the MemoryConstraint
579 memory_constraint = AcirConstraint{ .init = init_indices, .trace = {}, .type = BlockType::ReturnData };
580 }
581
583 [[maybe_unused]] AcirConstraint memory_constraint,
584 [[maybe_unused]] WitnessVector witness_values,
585 const InvalidWitness::Target& invalid_witness_target)
586 {
587 switch (invalid_witness_target) {
589 break;
590 }
591
592 return { memory_constraint, witness_values };
593 }
594};
595
596template <size_t ReturnDataSize_> class ReturnDataTestsParams {
597 public:
598 static constexpr size_t returndata_size = ReturnDataSize_;
599};
600
601template <typename Params>
602class ReturnDataTests : public ::testing::Test, public TestClass<ReturnDataTestingFunctions<Params::returndata_size>> {
603 protected:
605};
606
607using ReturnDataTestConfigs = testing::Types<ReturnDataTestsParams<0>, ReturnDataTestsParams<10>>;
608
610
611TYPED_TEST(ReturnDataTests, GenerateVKFromConstraints)
612{
613 TestFixture::template test_vk_independence<MegaFlavor>();
614}
testing::Types< RAMTestParams< UltraCircuitBuilder, 0, 0, 0, false >, RAMTestParams< UltraCircuitBuilder, 10, 0, 0, false >, RAMTestParams< UltraCircuitBuilder, 10, 0, 0, true >, RAMTestParams< UltraCircuitBuilder, 10, 0, 10, false >, RAMTestParams< UltraCircuitBuilder, 10, 0, 10, true >, RAMTestParams< UltraCircuitBuilder, 10, 10, 0, false >, RAMTestParams< UltraCircuitBuilder, 10, 10, 0, true >, RAMTestParams< UltraCircuitBuilder, 10, 20, 10, true >, RAMTestParams< MegaCircuitBuilder, 0, 0, 0, false >, RAMTestParams< MegaCircuitBuilder, 10, 0, 0, false >, RAMTestParams< MegaCircuitBuilder, 10, 0, 0, true >, RAMTestParams< MegaCircuitBuilder, 10, 0, 10, false >, RAMTestParams< MegaCircuitBuilder, 10, 0, 10, true >, RAMTestParams< MegaCircuitBuilder, 10, 10, 0, false >, RAMTestParams< MegaCircuitBuilder, 10, 10, 0, true >, RAMTestParams< MegaCircuitBuilder, 10, 20, 10, true > > RAMTestConfigs
testing::Types< CallDataTestParams< CallDataType::Primary, 0, 0, false >, CallDataTestParams< CallDataType::Primary, 10, 5, false >, CallDataTestParams< CallDataType::Primary, 10, 5, true >, CallDataTestParams< CallDataType::Secondary, 0, 0, false >, CallDataTestParams< CallDataType::Secondary, 10, 5, false >, CallDataTestParams< CallDataType::Secondary, 10, 5, true > > CallDataTestConfigs
void add_constant_ops(const size_t table_size, const std::vector< bb::fr > &table_values, WitnessVector &witness_values, std::vector< MemOp > &trace)
Utility method to add read/write operations with constant indices/values.
testing::Types< ROMTestParams< UltraCircuitBuilder, 0, 0, false >, ROMTestParams< UltraCircuitBuilder, 10, 0, false >, ROMTestParams< UltraCircuitBuilder, 10, 0, true >, ROMTestParams< UltraCircuitBuilder, 10, 20, false >, ROMTestParams< UltraCircuitBuilder, 10, 20, true >, ROMTestParams< MegaCircuitBuilder, 0, 0, false >, ROMTestParams< MegaCircuitBuilder, 10, 0, false >, ROMTestParams< MegaCircuitBuilder, 10, 0, true >, ROMTestParams< MegaCircuitBuilder, 10, 20, false >, ROMTestParams< MegaCircuitBuilder, 10, 20, true > > ROMTestConfigs
testing::Types< ReturnDataTestsParams< 0 >, ReturnDataTestsParams< 10 > > ReturnDataTestConfigs
static std::vector< std::string > get_labels()
static ProgramMetadata generate_metadata()
static std::pair< AcirConstraint, WitnessVector > invalidate_witness(AcirConstraint memory_constraint, WitnessVector witness_values, const InvalidWitness::Target &invalid_witness_target)
static void generate_constraints(AcirConstraint &memory_constraint, WitnessVector &witness_values)
static void SetUpTestSuite()
static void SetUpTestSuite()
static std::vector< Target > get_all()
static std::vector< std::string > get_labels()
static std::pair< AcirConstraint, WitnessVector > invalidate_witness(AcirConstraint memory_constraint, WitnessVector witness_values, const InvalidWitness::Target &invalid_witness_target)
static ProgramMetadata generate_metadata()
static void generate_constraints(AcirConstraint &memory_constraint, WitnessVector &witness_values)
static void SetUpTestSuite()
static std::vector< Target > get_all()
static std::vector< std::string > get_labels()
static void generate_constraints(AcirConstraint &memory_constraint, WitnessVector &witness_values)
static std::pair< AcirConstraint, WitnessVector > invalidate_witness(AcirConstraint memory_constraint, WitnessVector witness_values, const InvalidWitness::Target &invalid_witness_target)
static ProgramMetadata generate_metadata()
static std::vector< std::string > get_labels()
static ProgramMetadata generate_metadata()
static std::pair< AcirConstraint, WitnessVector > invalidate_witness(AcirConstraint memory_constraint, WitnessVector witness_values, const InvalidWitness::Target &invalid_witness_target)
static void generate_constraints(AcirConstraint &memory_constraint, WitnessVector &witness_values)
static constexpr size_t returndata_size
virtual uint32_t get_random_uint32()=0
TestTraceContainer trace
numeric::RNG & engine
std::vector< bb::fr > WitnessVector
std::vector< uint32_t > add_to_witness_and_track_indices(std::vector< bb::fr > &witness, const T &input)
Append values to a witness vector and track their indices.
Definition utils.hpp:90
RNG & get_debug_randomness(bool reset, std::uint_fast64_t seed)
Definition engine.cpp:190
std::filesystem::path bb_crs_path()
void init_file_crs_factory(const std::filesystem::path &path)
TYPED_TEST_SUITE(BoomerangRecursiveVerifierTest, Flavors)
field< Bn254FrParams > fr
Definition fr.hpp:174
TYPED_TEST(ShpleminiTest, CorrectnessOfMultivariateClaimBatching)
MegaCircuitBuilder_< field< Bn254FrParams > > MegaCircuitBuilder
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
static constexpr CallDataType calldata_type
static constexpr size_t calldata_size
static constexpr bool perform_constant_ops
static constexpr size_t num_reads
static constexpr bool perform_constant_ops
static constexpr size_t num_reads
static constexpr size_t num_writes
static constexpr size_t table_size
static constexpr bool perform_constant_ops
static constexpr size_t table_size
static constexpr size_t num_reads
Struct holding the data required to add memory constraints to a circuit.
std::vector< uint32_t > init
Memory operation. Index and value store the index of the memory location, and value is the value to b...
Metadata required to create a circuit.
static WitnessOrConstant from_index(uint32_t index)
static WitnessOrConstant from_constant(FF value)
static field random_element(numeric::RNG *engine=nullptr) noexcept