Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
gemini.test.cpp
Go to the documentation of this file.
2#include "gemini_impl.hpp"
3
4#include "../commitment_key.test.hpp"
5
6using namespace bb;
7
8template <class Curve> class GeminiTest : public CommitmentTest<Curve> {
11 using Fr = typename Curve::ScalarField;
15
16 public:
17 static constexpr size_t log_n = 4;
18 static constexpr size_t n = 1UL << log_n;
19
20 static constexpr size_t virtual_log_n = 6;
21
24
25 // is_big_ck is set to true in the high degree attack test. It uses a larger SRS size (big_ck_size=2^14) and allows
26 // the prover
27 // to commit to high degree polynomials (big_n=2^12).
28 bool is_big_ck = false;
29 static constexpr size_t big_n = 1UL << 12;
30 static constexpr size_t small_log_n = 3;
31 static constexpr size_t big_ck_size = 1 << 14;
32 inline static CK big_ck = create_commitment_key<CK>(big_ck_size);
33 bool is_reject_case = false;
34
35 static CK ck;
36 static VK vk;
37
38 static void SetUpTestSuite()
39 {
40 ck = create_commitment_key<CK>(n);
41 vk = create_verifier_commitment_key<VK>();
42 }
43
44 void execute_gemini_and_verify_claims(std::vector<Fr>& multilinear_evaluation_point,
45 MockClaimGenerator<Curve> mock_claims)
46 {
47 const size_t poly_size = is_big_ck ? big_n : n;
48 const CK& comkey = is_big_ck ? big_ck : ck;
49 const size_t multilinear_challenge_size = is_big_ck ? small_log_n : log_n;
50
51 auto prover_transcript = NativeTranscript::prover_init_empty();
52
53 // Compute:
54 // - (d+1) opening pairs: {r, \hat{a}_0}, {-r^{2^i}, a_i}, i = 0, ..., d-1
55 // - (d+1) Fold polynomials Fold_{r}^(0), Fold_{-r}^(0), and Fold^(i), i = 0, ..., d-1
56 auto prover_output = GeminiProver::prove(
57 poly_size, mock_claims.polynomial_batcher, multilinear_evaluation_point, comkey, prover_transcript);
58
59 // The prover output needs to be completed by adding the "positive" Fold claims, i.e. evaluations of Fold^(i) at
60 // r^{2^i} for i=1, ..., d-1. Although here we are copying polynomials, it is not the case when GeminiProver is
61 // combined with ShplonkProver.
62 std::vector<ProverOpeningClaim<Curve>> prover_claims_with_pos_evals;
63 // `prover_output` consists of d+1 opening claims, we add another d-1 claims for each positive evaluation
64 // Fold^i(r^{2^i}) for i = 1, ..., d-1
65 const size_t total_num_claims = 2 * multilinear_challenge_size;
66 prover_claims_with_pos_evals.reserve(total_num_claims);
67
68 for (auto& claim : prover_output) {
69 if (claim.gemini_fold) {
70 if (claim.gemini_fold) {
71 // "positive" evaluation challenge r^{2^i} for i = 1, ..., d-1
72 const Fr evaluation_challenge = -claim.opening_pair.challenge;
73 // Fold^(i) at r^{2^i} for i=1, ..., d-1
74 const Fr pos_evaluation = claim.polynomial.evaluate(evaluation_challenge);
75 // Add the positive Fold claims to the vector of claims
76 ProverOpeningClaim<Curve> pos_fold_claim = { .polynomial = claim.polynomial,
77 .opening_pair = { .challenge = evaluation_challenge,
78 .evaluation = pos_evaluation } };
79 prover_claims_with_pos_evals.emplace_back(pos_fold_claim);
80 }
81 }
82 prover_claims_with_pos_evals.emplace_back(claim);
83 }
84
85 // Check that the Fold polynomials have been evaluated correctly in the prover
86 this->verify_batch_opening_pair(prover_claims_with_pos_evals);
87
88 auto verifier_transcript = NativeTranscript::verifier_init_empty(prover_transcript);
89
90 // Compute:
91 // - d opening pairs: {r^{2^i}, \hat{a}_i} for i = 0, ..., d-1
92 // - 2 partially evaluated Fold polynomial commitments [Fold_{r}^(0)] and [Fold_{-r}^(0)]
93 // Aggregate: 2d opening pairs and 2d Fold poly commitments into verifier claim
94 auto verifier_claims = GeminiVerifier::reduce_verification(
95 multilinear_evaluation_point, mock_claims.claim_batcher, verifier_transcript);
96
97 // Check equality of the opening pairs computed by prover and verifier
98 if (this->is_reject_case) {
99 bool mismatch = false;
100 for (auto [prover_claim, verifier_claim] : zip_view(prover_claims_with_pos_evals, verifier_claims)) {
101 if (prover_claim.opening_pair != verifier_claim.opening_pair) {
102 mismatch = true;
103 break;
104 }
105 }
106 EXPECT_TRUE(mismatch) << "Expected a mismatch in opening pairs, but all matched.";
107 } else {
108 for (auto [prover_claim, verifier_claim] : zip_view(prover_claims_with_pos_evals, verifier_claims)) {
109 this->verify_opening_claim(verifier_claim, prover_claim.polynomial, comkey);
110 ASSERT_EQ(prover_claim.opening_pair, verifier_claim.opening_pair);
111 }
112 }
113 }
114
116 {
117 auto prover_transcript = NativeTranscript::prover_init_empty();
118
119 auto u = this->random_evaluation_point(virtual_log_n);
120
121 Polynomial<Fr> poly((1UL << log_n));
122
123 poly.at(0) = 1;
124 poly.at(1) = 2;
125 poly.at(2) = 3;
126
127 typename GeminiProver::PolynomialBatcher poly_batcher(1UL << log_n);
128 poly_batcher.set_unshifted(RefVector(poly));
129
130 // As we are opening `poly` extended by zero from `log_n` dimensions to `virtual_log_n` dimensions, it needs to
131 // be multiplied by appropriate scalars.
132 Fr eval = poly.evaluate_mle(std::span(u).subspan(0, log_n)) * (Fr(1) - u[virtual_log_n - 1]) *
133 (Fr(1) - u[virtual_log_n - 2]);
134 auto comm = ck.commit(poly);
135 auto claim_batcher = ClaimBatcher{ .unshifted = ClaimBatch{ RefVector(comm), RefVector(eval) } };
136
137 // Compute:
138 // - (d+1) opening pairs: {r, \hat{a}_0}, {-r^{2^i}, a_i}, i = 0, ..., d-1
139 // - (d+1) Fold polynomials Fold_{r}^(0), Fold_{-r}^(0), and Fold^(i), i = 0, ..., d-1
140 auto prover_output = GeminiProver::prove(1UL << log_n, poly_batcher, u, ck, prover_transcript);
141
142 // The prover output needs to be completed by adding the "positive" Fold claims, i.e. evaluations of
143 // Fold^(i) at r^{2^i} for i=1, ..., d-1. Although here we are copying polynomials, it is not the case when
144 // GeminiProver is combined with ShplonkProver.
145 std::vector<ProverOpeningClaim<Curve>> prover_claims_with_pos_evals;
146 // `prover_output` consists of d+1 opening claims, we add another d-1 claims for each positive evaluation
147 // Fold^i(r^{2^i}) for i = 1, ..., d-1
148 const size_t total_num_claims = 2 * virtual_log_n;
149 prover_claims_with_pos_evals.reserve(total_num_claims);
150
151 for (auto& claim : prover_output) {
152 if (claim.gemini_fold) {
153 if (claim.gemini_fold) {
154 // "positive" evaluation challenge r^{2^i} for i = 1, ..., d-1
155 const Fr evaluation_challenge = -claim.opening_pair.challenge;
156 // Fold^(i) at r^{2^i} for i=1, ..., d-1
157 const Fr pos_evaluation = claim.polynomial.evaluate(evaluation_challenge);
158
159 // Add the positive Fold claims to the vector of claims
160 ProverOpeningClaim<Curve> pos_fold_claim = { .polynomial = claim.polynomial,
161 .opening_pair = { .challenge = evaluation_challenge,
162 .evaluation = pos_evaluation } };
163 prover_claims_with_pos_evals.emplace_back(pos_fold_claim);
164 }
165 }
166 prover_claims_with_pos_evals.emplace_back(claim);
167 }
168
169 // Check that the Fold polynomials have been evaluated correctly in the prover
170 this->verify_batch_opening_pair(prover_claims_with_pos_evals);
171
172 auto verifier_transcript = NativeTranscript::verifier_init_empty(prover_transcript);
173
174 // Compute:
175 // - d opening pairs: {r^{2^i}, \hat{a}_i} for i = 0, ..., d-1
176 // - 2 partially evaluated Fold polynomial commitments [Fold_{r}^(0)] and [Fold_{-r}^(0)]
177 // Aggregate: 2d opening pairs and 2d Fold poly commitments into verifier claim
178 auto verifier_claims = GeminiVerifier::reduce_verification(u, claim_batcher, verifier_transcript);
179 // Check equality of the opening pairs computed by prover and verifier
180 for (auto [prover_claim, verifier_claim] : zip_view(prover_claims_with_pos_evals, verifier_claims)) {
181 this->verify_opening_claim(verifier_claim, prover_claim.polynomial, ck);
182 ASSERT_EQ(prover_claim.opening_pair, verifier_claim.opening_pair);
183 }
184 }
185}
186
187;
188
189using ParamsTypes = ::testing::Types<curve::BN254, curve::Grumpkin>;
191
193{
194 auto u = this->random_evaluation_point(this->log_n);
195 MockClaimGenerator mock_claims(
196 this->n, /*num_polynomials*/ 1, /*num_to_be_shifted*/ 0, /*num_to_be_right_shifted_by_k*/ 0, u, this->ck);
197
198 this->execute_gemini_and_verify_claims(u, mock_claims);
199}
200
202{
203 auto u = this->random_evaluation_point(this->log_n);
204
205 MockClaimGenerator mock_claims(
206 this->n, /*num_polynomials*/ 1, /*num_to_be_shifted*/ 1, /*num_to_be_right_shifted_by_k*/ 0, u, this->ck);
207
208 this->execute_gemini_and_verify_claims(u, mock_claims);
209}
210
212{
213
214 auto u = this->random_evaluation_point(this->log_n);
215
216 MockClaimGenerator mock_claims(
217 this->n, /*num_polynomials*/ 2, /*num_to_be_shifted*/ 0, /*num_to_be_right_shifted_by_k*/ 0, u, this->ck);
218
219 this->execute_gemini_and_verify_claims(u, mock_claims);
220}
221
222TYPED_TEST(GeminiTest, DoubleWithShift)
223{
224
225 auto u = this->random_evaluation_point(this->log_n);
226
227 MockClaimGenerator mock_claims(
228 this->n, /*num_polynomials*/ 2, /*num_to_be_shifted*/ 1, /*num_to_be_right_shifted_by_k*/ 0, u, this->ck);
229
230 this->execute_gemini_and_verify_claims(u, mock_claims);
231}
232
233TYPED_TEST(GeminiTest, DoubleWithShiftAndInterleaving)
234{
235 auto u = this->random_evaluation_point(this->log_n);
236
237 MockClaimGenerator mock_claims(this->n,
238 /*num_polynomials*/ 2,
239 /*num_to_be_shifted*/ 0,
240 /*num_to_be_right_shifted_by_k*/ 0,
241 u,
242 this->ck,
243 /*num_interleaved*/ 3,
244 /*num_to_be_interleaved*/ 2);
245
246 this->execute_gemini_and_verify_claims(u, mock_claims);
247}
248
249TYPED_TEST(GeminiTest, OpenExtensionByZero)
250{
251 TestFixture::open_extension_by_zero();
252}
257TYPED_TEST(GeminiTest, SoundnessRegression)
258{
259 using ClaimBatcher = ClaimBatcher_<TypeParam>;
260 using ClaimBatch = ClaimBatcher::Batch;
261 using Claim = ProverOpeningClaim<TypeParam>;
262 using Commitment = TypeParam::AffineElement;
263 using Fr = TypeParam::ScalarField;
264 const size_t log_n = 3;
265 const size_t n = 8;
266
267 auto prover_transcript = NativeTranscript::prover_init_empty();
268 const Fr rho = prover_transcript->template get_challenge<Fr>("rho");
269 std::vector<Polynomial<Fr>> fold_polynomials;
270 fold_polynomials.reserve(log_n);
271
272 Polynomial<Fr> fold_0(n);
273 Polynomial<Fr> fold_1(n / 2);
274 Polynomial<Fr> fold_2(n / 4);
275
276 auto u = this->random_evaluation_point(log_n);
277
278 // Generate a random evaluation v, the prover claims that `zero_polynomial`(u) = v
279 Fr claimed_multilinear_eval = Fr::random_element();
280
281 // Go through the Gemini Prover steps: compute fold polynomials and their evaluations
282 std::vector<Fr> fold_evals;
283 fold_evals.reserve(log_n);
284
285 // By defining the coefficients of fold polynomials as below, a malicious prover can make sure that the values
286 // fold₁(r²) = 0, and hence fold₀(r), computed by the verifier, are 0. At the same time, the prover can open
287 // fold₁(-r²) and fold₂(-r⁴)`to their honest value.
288
289 // fold₂[0] = claimed_multilinear_eval ⋅ u₁² ⋅ [(1 - u₂) ⋅ u₁² - u₂ ⋅ (1 - u₁)²]⁻¹
290 fold_2.at(0) =
291 claimed_multilinear_eval * u[1].sqr() * ((Fr(1) - u[2]) * u[1].sqr() - u[2] * (Fr(1) - u[1]).sqr()).invert();
292 // fold₂[1] = - (1 - u₁)² ⋅ fold₂[0] ⋅ u₁⁻²
293 fold_2.at(1) = -(Fr(1) - u[1]).sqr() * fold_2.at(0) * (u[1].sqr()).invert();
294
295 // The coefficients of fold_1 are determined by the constant term of fold_2.
296 fold_1.at(0) = Fr(0);
297 fold_1.at(1) = Fr(2) * fold_2.at(0) * u[1].invert(); // fold₁[1] = 2 ⋅ fold₂[0] / u₁
298 fold_1.at(2) = -(Fr(1) - u[1]) * fold_1.at(1) * u[1].invert(); // fold₁[2] = -(1 - u₁) ⋅ fold₁[1] / u₁
299 fold_1.at(3) = Fr(0);
300
301 prover_transcript->send_to_verifier("Gemini:FOLD_1", this->ck.commit(fold_1));
302 prover_transcript->send_to_verifier("Gemini:FOLD_2", this->ck.commit(fold_2));
303
304 // Get Gemini evaluation challenge
305 const Fr gemini_r = prover_transcript->template get_challenge<Fr>("Gemini:r");
306
307 // Place honest eval of fold₀(-r) to the vector of evals
308 fold_evals.emplace_back(fold_0.evaluate(-gemini_r));
309
310 // Compute univariate opening queries rₗ = r^{2ˡ} for l = 0, 1, 2
311 std::vector<Fr> r_squares = gemini::powers_of_evaluation_challenge(gemini_r, log_n);
312
313 // Compute honest evaluations fold₁(-r²) and fold₂(-r⁴)
314 fold_evals.emplace_back(fold_1.evaluate(-r_squares[1]));
315 fold_evals.emplace_back(fold_2.evaluate(-r_squares[2]));
316 prover_transcript->send_to_verifier("Gemini:a_1", fold_evals[0]);
317 prover_transcript->send_to_verifier("Gemini:a_2", fold_evals[1]);
318 prover_transcript->send_to_verifier("Gemini:a_3", fold_evals[2]);
319
320 // Compute the powers of r used by the verifier. It is an artifact of the const proof size logic.
321 const std::vector<Fr> gemini_eval_challenge_powers = gemini::powers_of_evaluation_challenge(gemini_r, log_n);
322
323 std::vector<Claim> prover_opening_claims;
324 prover_opening_claims.reserve(2 * log_n);
325
326 prover_opening_claims.emplace_back(Claim{ fold_0, { gemini_r, Fr{ 0 } } });
327 prover_opening_claims.emplace_back(Claim{ fold_0, { -gemini_r, Fr{ 0 } } });
328 prover_opening_claims.emplace_back(Claim{ fold_1, { r_squares[1], fold_1.evaluate(r_squares[1]) } });
329 prover_opening_claims.emplace_back(Claim{ fold_1, { -r_squares[1], fold_evals[1] } });
330 prover_opening_claims.emplace_back(Claim{ fold_2, { r_squares[2], fold_2.evaluate(r_squares[2]) } });
331 prover_opening_claims.emplace_back(Claim{ fold_2, { -r_squares[2], fold_evals[2] } });
332
333 // Check that the Fold polynomials have been evaluated correctly in the prover
334 this->verify_batch_opening_pair(prover_opening_claims);
335
336 auto verifier_transcript = NativeTranscript::verifier_init_empty(prover_transcript);
337
338 std::vector<Commitment> unshifted_commitments = { this->ck.commit(fold_0) };
339 std::vector<Fr> unshifted_evals = { claimed_multilinear_eval * rho.pow(0) };
340
341 ClaimBatcher claim_batcher{ .unshifted =
342 ClaimBatch{ RefVector(unshifted_commitments), RefVector(unshifted_evals) } };
343
344 auto verifier_claims = GeminiVerifier_<TypeParam>::reduce_verification(u, claim_batcher, verifier_transcript);
345
346 // Malicious prover could honestly prove all "negative" evaluations and several "positive evaluations". In
347 // particular, the evaluation of `fold_0` at r.
348 std::vector<size_t> matching_claim_indices{ 0, 1, 3, 4, 5 };
349 // However, the evaluation of `fold_1` at r^2 that the verifier computes assuming that fold has been performed
350 // correctly, does not match the actual evaluation of tampered fold_1 that the prover can open.
351 std::vector<size_t> mismatching_claim_indices = { 2 };
352 for (auto idx : matching_claim_indices) {
353 EXPECT_TRUE(prover_opening_claims[idx].opening_pair == verifier_claims[idx].opening_pair);
354 }
355
356 // The mismatch in claims below leads to Gemini and Shplemini Verifier rejecting the tampered proof and confirms
357 // the necessity of opening `fold_i` at r^{2^i} for i = 1, ..., log_n - 1.
358 for (auto idx : mismatching_claim_indices) {
359 EXPECT_FALSE(prover_opening_claims[idx].opening_pair == verifier_claims[idx].opening_pair);
360 }
361}
362
363// The prover commits to a higher degree polynomial than what is expected. The test considers the case where
364// this polynomial folds down to a constant (equal to the claimed evaluation) after the expected number of rounds
365// (due to the choice of the evaluation point). In this case, the verifier accepts.
366TYPED_TEST(GeminiTest, HighDegreeAttackAccept)
367{
368 using Fr = typename TypeParam::ScalarField;
369
370 this->is_big_ck = true;
371
372 // Sample public opening point (u_0, u_1, u_2)
373 auto u = this->random_evaluation_point(this->small_log_n);
374
375 // Choose a claimed eval at `u`
376 Fr claimed_multilinear_eval = Fr::random_element();
377
378 // poly is of high degrees, as the SRS allows for it
379 Polynomial<Fr> poly(this->big_n);
380
381 // Define poly to be of a specific form such that after small_log_n folds with u, it becomes a constant equal to
382 // claimed_multilinear_eval.
383 const Fr tail = ((Fr(1) - u[0]) * (Fr(1) - u[1])).invert();
384 poly.at(4) = claimed_multilinear_eval * tail / u[2];
385 poly.at(4088) = tail;
386 poly.at(4092) = -tail * (Fr(1) - u[2]) / u[2];
387
389 this->big_n, std::vector{ std::move(poly) }, std::vector<Fr>{ claimed_multilinear_eval }, this->big_ck);
390
391 this->execute_gemini_and_verify_claims(u, mock_claims);
392}
393
394// The prover commits to a higher degree polynomial than what is expected. The test considers the case where
395// this polynomial does not fold down to a constant after the expected number of rounds. In this case, the verifier
396// rejects with high probabililty.
397TYPED_TEST(GeminiTest, HighDegreeAttackReject)
398{
399 using Fr = typename TypeParam::ScalarField;
401
402 this->is_big_ck = true;
403 this->is_reject_case = true;
404
405 // poly of high degree, as SRS allows for it
406 Polynomial poly = Polynomial::random(this->big_n);
407
408 // Sample public opening point (u_0, u_1, u_2)
409 auto u = this->random_evaluation_point(this->small_log_n);
410
411 // Choose a claimed eval at `u`
412 Fr claimed_multilinear_eval = Fr::random_element();
413
415 this->big_n, std::vector{ std::move(poly) }, std::vector<Fr>{ claimed_multilinear_eval }, this->big_ck);
416
417 this->execute_gemini_and_verify_claims(u, mock_claims);
418}
419
420template <class Curve> typename GeminiTest<Curve>::CK GeminiTest<Curve>::ck;
421template <class Curve> typename GeminiTest<Curve>::VK GeminiTest<Curve>::vk;
static VK vk
typename Curve::AffineElement Commitment
static void SetUpTestSuite()
void execute_gemini_and_verify_claims(std::vector< Fr > &multilinear_evaluation_point, MockClaimGenerator< Curve > mock_claims)
void open_extension_by_zero()
static constexpr size_t log_n
static CK big_ck
static constexpr size_t n
static constexpr size_t small_log_n
static CK ck
static constexpr size_t big_n
static constexpr size_t big_ck_size
bool is_reject_case
static constexpr size_t virtual_log_n
typename Curve::ScalarField Fr
static std::shared_ptr< BaseTranscript > verifier_init_empty(const std::shared_ptr< BaseTranscript > &transcript)
For testing: initializes transcript based on proof data then receives junk data produced by BaseTrans...
static std::shared_ptr< BaseTranscript > prover_init_empty()
For testing: initializes transcript with some arbitrary data so that a challenge can be generated aft...
CommitmentKey object over a pairing group 𝔾₁.
Commitment commit(PolynomialSpan< const Fr > polynomial) const
Uses the ProverSRS to create a commitment to p(X)
void verify_batch_opening_pair(std::vector< ProverOpeningClaim< Curve > > opening_claims)
Ensures that a set of opening pairs is correct by checking that evaluations are correct by recomputin...
std::vector< Fr > random_evaluation_point(const size_t num_variables)
void verify_opening_claim(const OpeningClaim< Curve > &claim, const Polynomial &witness, CommitmentKey< Curve > ck=CommitmentKey< Curve >())
Class responsible for computation of the batched multilinear polynomials required by the Gemini proto...
Definition gemini.hpp:129
void set_unshifted(RefVector< Polynomial > polynomials)
Definition gemini.hpp:166
static std::vector< Claim > prove(const Fr circuit_size, PolynomialBatcher &polynomial_batcher, std::span< Fr > multilinear_challenge, const CommitmentKey< Curve > &commitment_key, const std::shared_ptr< Transcript > &transcript, bool has_zk=false)
static std::vector< OpeningClaim< Curve > > reduce_verification(std::span< Fr > multilinear_challenge, ClaimBatcher &claim_batcher, auto &transcript)
Returns univariate opening claims for the Fold polynomials to be checked later.
Definition gemini.hpp:408
Structured polynomial class that represents the coefficients 'a' of a_0 + a_1 x .....
Fr evaluate(const Fr &z, size_t target_size) const
Fr evaluate_mle(std::span< const Fr > evaluation_points, bool shift=false) const
evaluate multi-linear extension p(X_0,…,X_{n-1}) = \sum_i a_i*L_i(X_0,…,X_{n-1}) at u = (u_0,...
Fr & at(size_t index)
Our mutable accessor, unlike operator[]. We abuse precedent a bit to differentiate at() and operator[...
Polynomial p and an opening pair (r,v) such that p(r) = v.
Definition claim.hpp:34
Polynomial polynomial
Definition claim.hpp:39
A template class for a reference vector. Behaves as if std::vector<T&> was possible.
typename Group::affine_element AffineElement
Definition grumpkin.hpp:56
::testing::Types< curve::BN254, curve::Grumpkin > ParamsTypes
TYPED_TEST(GeminiTest, Single)
TYPED_TEST_SUITE(GeminiTest, ParamsTypes)
std::vector< Fr > powers_of_evaluation_challenge(const Fr r, const size_t num_squares)
Compute squares of folding challenge r.
Definition gemini.hpp:94
Entry point for Barretenberg command-line interface.
CommitmentKey< Curve > ck
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
Curve::ScalarField Fr
Logic to support batching opening claims for unshifted and shifted polynomials in Shplemini.
std::optional< Batch > unshifted
Constructs random polynomials, computes commitments and corresponding evaluations.
BB_INLINE constexpr field pow(const uint256_t &exponent) const noexcept
constexpr field invert() const noexcept
static field random_element(numeric::RNG *engine=nullptr) noexcept
BB_INLINE constexpr field sqr() const noexcept