Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
field_conversion.test.cpp
Go to the documentation of this file.
5#include <gtest/gtest.h>
6
8
10
11template <typename Builder> using fr = field_t<Builder>;
12template <typename Builder> using fq = bigfield<Builder, bb::Bn254FqParams>;
14template <typename Builder> using grumpkin_element = cycle_group<Builder>;
15
16template <typename Builder> class stdlib_field_conversion : public ::testing::Test {
17 public:
19
26 template <typename T, typename CreateFn>
27 void check_deserialization_gate_count(CreateFn create_native, uint32_t expected_gates, size_t num_elements = 1)
28 {
29 using NativeCodec = FrCodec;
31
32 for (size_t i = 0; i < num_elements; ++i) {
33 // Create native value and serialize
34 auto native_value = create_native();
35 auto native_fields = NativeCodec::serialize_to_fields(native_value);
36
37 // Create witnesses from "proof data"
38 std::vector<field_t<Builder>> witness_fields;
39 for (const auto& f : native_fields) {
40 witness_fields.push_back(field_t<Builder>::from_witness(&builder, f));
41 }
42
43 // Deserialize in circuit
44 [[maybe_unused]] auto deserialized = Codec::template deserialize_from_fields<T>(witness_fields);
45 }
46
47 check_circuit_and_gate_count(builder, expected_gates);
48 }
49
50 // Serialize and deserialize
51 template <typename T> void check_conversion(T in, bool valid_circuit = true)
52 {
53 size_t len = Codec::template calc_num_fields<T>();
54 auto frs = Codec::serialize_to_fields(in);
55 EXPECT_EQ(len, frs.size());
56 auto out = Codec::template deserialize_from_fields<T>(frs);
57
58 EXPECT_EQ(in.get_value(), out.get_value());
59
60 auto ctx = in.get_context();
61
62 EXPECT_EQ(CircuitChecker::check(*ctx), valid_circuit);
63 }
64
65 template <typename T> void check_conversion_iterable(T x)
66 {
67 size_t len = Codec::template calc_num_fields<T>();
68 auto frs = Codec::template serialize_to_fields<T>(x);
69 EXPECT_EQ(len, frs.size());
70 auto y = Codec::template deserialize_from_fields<T>(frs);
71 EXPECT_EQ(x.size(), y.size());
72 for (auto [val1, val2] : zip_view(x, y)) {
73 EXPECT_EQ(val1.get_value(), val2.get_value());
74 }
75 }
76};
77
78using BuilderTypes = testing::Types<UltraCircuitBuilder, MegaCircuitBuilder>;
79
81
86{
87 using Builder = TypeParam;
89 bb::fr field_element_val(
90 std::string("9a807b615c4d3e2fa0b1c2d3e4f56789fedcba9876543210abcdef0123456789")); // 256 bits
91 fr<Builder> field_element(&builder, field_element_val);
92 this->check_conversion(field_element);
93
94 field_element_val = bb::fr::modulus_minus_two; // modulus - 2
95 field_element = fr<Builder>(&builder, field_element_val);
96 this->check_conversion(field_element);
97
98 field_element_val = bb::fr(1);
99 field_element = fr<Builder>(&builder, field_element_val);
100 this->check_conversion(field_element);
101}
102
106TYPED_TEST(stdlib_field_conversion, FieldConversionGrumpkinFr)
107{
108 using Builder = TypeParam;
110
111 // Constructing bigfield objects with bb::fq values
112 bb::fq field_element_val(
113 std::string("9a807b615c4d3e2fa0b1c2d3e4f56789fedcba9876543210abcdef0123456789")); // 256 bits
114 this->check_conversion(fq<Builder>::from_witness(&builder, field_element_val));
115}
116
121TYPED_TEST(stdlib_field_conversion, FieldConversionBN254AffineElement)
122{
123 using Builder = TypeParam;
124 { // Serialize and deserialize the bn254 generator
126
127 bn254_element<Builder> group_element =
128 bn254_element<Builder>::from_witness(&builder, curve::BN254::AffineElement::one());
129 this->check_conversion(group_element);
130 }
131 { // Serialize and deserialize a valid bn254 point
133
135 bn254_element<Builder> group_element = bn254_element<Builder>::from_witness(&builder, group_element_val);
136 this->check_conversion(group_element);
137 }
138
139 { // Serialize and deserialize random Grumpkin points
141 const size_t num_points = 50;
142 const curve::BN254::AffineElement native_generator = curve::BN254::AffineElement::one();
143
144 for (size_t i = 0; i < num_points; i++) {
145 bb::fr random_scalar = bb::fr::random_element();
146 bn254_element<Builder> group_element =
147 bn254_element<Builder>::from_witness(&builder, native_generator * random_scalar);
148 this->check_conversion(group_element);
149 }
150 }
151 { // Serialize and deserialize the point at infinity
153
154 bn254_element<Builder> group_element =
155 bn254_element<Builder>::from_witness(&builder, curve::BN254::AffineElement::infinity());
156 // Point at infinity is now consistently represented as (0, 0) across all builders
157 this->check_conversion(group_element);
158 }
159
160 { // Serialize and deserialize "coordinates" that do not correspond to any point on the curve
162
163 curve::BN254::AffineElement group_element_val(1, 4);
164 bn254_element<Builder> group_element;
167 "");
168 } else {
169 group_element = bn254_element<Builder>::from_witness(&builder, group_element_val);
170 this->check_conversion(group_element);
171 }
172 }
173}
174
179TYPED_TEST(stdlib_field_conversion, FieldConversionGrumpkinAffineElement)
180{
181 using Builder = TypeParam;
182
183 { // Serialize and deserialize the Grumpkin generator
185 grumpkin_element<Builder> group_element =
186 grumpkin_element<Builder>::from_witness(&builder, curve::Grumpkin::AffineElement::one());
187 this->check_conversion(group_element);
188 }
189 { // Serialize and deserialize random Grumpkin points
191 const size_t num_points = 50;
192 const curve::Grumpkin::AffineElement native_generator = curve::Grumpkin::AffineElement::one();
193
194 for (size_t i = 0; i < num_points; i++) {
195 bb::fq random_scalar = bb::fq::random_element();
196 grumpkin_element<Builder> group_element =
197 grumpkin_element<Builder>::from_witness(&builder, native_generator * random_scalar);
198 this->check_conversion(group_element);
199 }
200 }
201
202 { // Serialize and deserialize "coordinates" that do not correspond to any point on the curve
203 BB_DISABLE_ASSERTS(); // Avoid on_curve assertion failure in cycle_group constructor
205
206 curve::Grumpkin::AffineElement group_element_val(12, 100);
208 this->check_conversion(group_element, /* valid circuit */ false);
209 }
210
211 { // Serialize and deserialize the point at infinity
213
214 // from_witness handles infinity: coordinates are set to (0,0), and the 2-arg constructor
215 // auto-detects infinity from x^2 + 5*y^2 == 0.
216 grumpkin_element<Builder> group_element =
217 grumpkin_element<Builder>::from_witness(&builder, curve::Grumpkin::AffineElement::infinity());
218 this->check_conversion(group_element);
219 }
220}
221
222TYPED_TEST(stdlib_field_conversion, DeserializePointAtInfinity)
223{
224 using Builder = TypeParam;
225 using Codec = StdlibCodec<field_t<Builder>>;
228
229 {
230 std::vector<fr<Builder>> zeros(4, zero);
231
232 bn254_element<Builder> point_at_infinity =
233 Codec::template deserialize_from_fields<bn254_element<Builder>>(zeros);
234
235 EXPECT_TRUE(point_at_infinity.get_value().is_point_at_infinity());
236 EXPECT_TRUE(CircuitChecker::check(builder));
237 }
238 {
239 std::vector<fr<Builder>> zeros(2, zero);
240
241 grumpkin_element<Builder> point_at_infinity =
242 Codec::template deserialize_from_fields<grumpkin_element<Builder>>(zeros);
243
244 EXPECT_TRUE(point_at_infinity.get_value().is_point_at_infinity());
245 EXPECT_TRUE(CircuitChecker::check(builder));
246 }
247}
248
252TYPED_TEST(stdlib_field_conversion, FieldConversionArrayBn254Fr)
253{
254 using Builder = TypeParam;
256
257 // Constructing std::array objects with fr<Builder> values
258 std::array<fr<Builder>, 4> array_of_frs_4{
260 };
261 this->check_conversion_iterable(array_of_frs_4);
262
265 fr<Builder>(&builder, 215215125),
266 fr<Builder>(&builder, 102701750),
267 fr<Builder>(&builder, 367032),
268 fr<Builder>(&builder, 12985028),
270 this->check_conversion_iterable(array_of_frs_7);
271}
272
276TYPED_TEST(stdlib_field_conversion, FieldConversionArrayGrumpkinFr)
277{
278 using Builder = TypeParam;
280
281 // Constructing std::array objects with fq<Builder> values
282 std::array<fq<Builder>, 4> array_of_fqs_4{
284 &builder,
285 static_cast<bb::fq>(std::string("9a807b615c4d3e2fa0b1c2d3e4f56789fedcba9876543210abcdef0123456789"))),
287 &builder,
288 static_cast<bb::fq>(std::string("2bf1eaf87f7d27e8dc4056e9af975985bccc89077a21891d6c7b6ccce0631f95"))),
290 &builder,
291 static_cast<bb::fq>(std::string("9a807b615c4d3e2fa0b1c2d3e4f56789fedcba9876543210abcdef0123456789"))),
293 &builder,
294 static_cast<bb::fq>(std::string("018555a8eb50cf07f64b019ebaf3af3c925c93e631f3ecd455db07bbb52bbdd3"))),
295 };
296 this->check_conversion_iterable(array_of_fqs_4);
297}
298
302TYPED_TEST(stdlib_field_conversion, FieldConversionUnivariateBn254Fr)
303{
304 using Builder = TypeParam;
306
307 // Constructing Univariate objects with fr<Builder> values
308 Univariate<fr<Builder>, 4> univariate{
310 };
311 this->check_conversion_iterable(univariate);
312}
313
317TYPED_TEST(stdlib_field_conversion, FieldConversionUnivariateGrumpkinFr)
318{
319 using Builder = TypeParam;
321
322 // Constructing std::array objects with fq<Builder> values
323 Univariate<fq<Builder>, 4> univariate{
325 &builder,
326 static_cast<bb::fq>(std::string("9a807b615c4d3e2fa0b1c2d3e4f56789fedcba9876543210abcdef0123456789"))),
328 &builder,
329 static_cast<bb::fq>(std::string("2bf1eaf87f7d27e8dc4056e9af975985bccc89077a21891d6c7b6ccce0631f95"))),
331 &builder,
332 static_cast<bb::fq>(std::string("018555a8eb50cf07f64b019ebaf3af3c925c93e631f3ecd455db07bbb52bbdd3"))),
334 &builder,
335 static_cast<bb::fq>(std::string("2bf1eaf87f7d27e8dc4056e9af975985bccc89077a21891d6c7b6ccce0631f95"))) }
336 };
337 this->check_conversion_iterable(univariate);
338}
339
340// ============================================================================
341// Gate Count Tests for Deserialization Operations
342// ============================================================================
343
348TYPED_TEST(stdlib_field_conversion, GateCountScalarDeserialization)
349{
350 // Scalar deserialization adds no gates (just witness creation)
351 this->template check_deserialization_gate_count<fr<TypeParam>>([] { return bb::fr::random_element(); }, 0);
352}
353
357TYPED_TEST(stdlib_field_conversion, GateCountBigfieldDeserialization)
358{
359 // Deserializing a single bigfield element is expensive due to creating new ranges for range constraints
360 this->template check_deserialization_gate_count<fq<TypeParam>>([] { return bb::fq::random_element(); }, 3515);
361}
362
367TYPED_TEST(stdlib_field_conversion, GateCountMultipleBigfieldDeserialization)
368{
369 this->template check_deserialization_gate_count<fq<TypeParam>>([] { return bb::fq::random_element(); }, 3914, 10);
370}
371
376TYPED_TEST(stdlib_field_conversion, GateCountBN254PointDeserialization)
377{
378 using Builder = TypeParam;
379 // Ultra: full bigfield construction + on-curve validation + assert_is_in_field for x and y
380 // Mega: no in-circuit checks; range constraint and on_curve validation deferred to ECCVM and Translator
381 constexpr uint32_t expected = std::is_same_v<Builder, bb::UltraCircuitBuilder> ? 3865 : 0;
382 this->template check_deserialization_gate_count<bn254_element<Builder>>(
383 [] { return curve::BN254::AffineElement::random_element(); }, expected);
384}
385
389TYPED_TEST(stdlib_field_conversion, GateCountMultipleBN254PointDeserialization)
390{
391 using Builder = TypeParam;
392
393 constexpr uint32_t expected = std::is_same_v<Builder, bb::UltraCircuitBuilder> ? 5751 : 0;
394 this->template check_deserialization_gate_count<bn254_element<Builder>>(
395 [] { return curve::BN254::AffineElement::random_element(); }, expected, 10);
396}
397
402TYPED_TEST(stdlib_field_conversion, GateCountGrumpkinPointDeserialization)
403{
404 this->template check_deserialization_gate_count<grumpkin_element<TypeParam>>(
405 [] { return curve::Grumpkin::AffineElement::random_element(); }, 10);
406}
407
412TYPED_TEST(stdlib_field_conversion, GateCountArrayDeserialization)
413{
414 constexpr size_t SIZE = 8;
415 this->template check_deserialization_gate_count<std::array<fr<TypeParam>, SIZE>>(
416 [] {
418 for (size_t i = 0; i < SIZE; ++i) {
419 arr[i] = bb::fr::random_element();
420 }
421 return arr;
422 },
423 0);
424}
425
430TYPED_TEST(stdlib_field_conversion, GateCountUnivariateDeserialization)
431{
432 constexpr size_t LENGTH = 8;
433 this->template check_deserialization_gate_count<Univariate<fr<TypeParam>, LENGTH>>(
434 [] {
436 for (size_t i = 0; i < LENGTH; ++i) {
437 evals[i] = bb::fr::random_element();
438 }
439 return Univariate<bb::fr, LENGTH>(evals);
440 },
441 0);
442}
443
450TYPED_TEST(stdlib_field_conversion, BigfieldDeserializationFailsOnLimbOverflow)
451{
452 using Builder = TypeParam;
453 using Codec = StdlibCodec<field_t<Builder>>;
454
455 bb::fr low_limb = bb::fr(0);
456 // 2^136 placed in high_limb position far exceeds the 2^118 bound for high limbs
457 bb::fr high_limb = bb::fr(uint256_t(1) << (2 * fq<Builder>::NUM_LIMB_BITS));
458
459 // Test 1: Native codec should reject via BB_ASSERT (asserts enabled)
460 {
461 std::vector<bb::fr> native_fields = { low_limb, high_limb };
462 EXPECT_THROW(FrCodec::deserialize_from_fields<bb::fq>(native_fields), std::runtime_error);
463 }
464
465 // Test 2: Circuit codec should reject via circuit constraints (disable asserts to bypass bigfield constructor
466 // checks)
467 {
472
473 // Deserialize as bigfield - this creates the bigfield from the two limbs
474 [[maybe_unused]] auto bigfield_val = Codec::template deserialize_from_fields<fq<Builder>>(circuit_fields);
475
476 // Circuit should fail validation
477 EXPECT_FALSE(CircuitChecker::check(builder));
478 }
479}
480
481// ============================================================================
482// Codec Consistency Tests: Verify FrCodec and StdlibCodec behave identically
483// ============================================================================
484
491TYPED_TEST(stdlib_field_conversion, BothCodecsRejectPointAtInfinityAlias)
492{
493 using Builder = TypeParam;
494 using Codec = StdlibCodec<field_t<Builder>>;
497
498 constexpr uint64_t NUM_LIMB_BITS = 68;
499 const uint256_t modulus = bb::fq::modulus;
500
501 // Create alias coordinates: x = modulus, y = modulus
502 const uint256_t x_lo = modulus & ((uint256_t(1) << (NUM_LIMB_BITS * 2)) - 1);
503 const uint256_t x_hi = modulus >> (NUM_LIMB_BITS * 2);
504
505 // Test 1: Native codec rejects via on_curve check
506 {
507 std::vector<bb::fr> native_fields = { bb::fr(x_lo), bb::fr(x_hi), bb::fr(x_lo), bb::fr(x_hi) };
508 EXPECT_THROW(FrCodec::deserialize_from_fields<curve::BN254::AffineElement>(native_fields), std::runtime_error);
509 }
510
511 // Test 2: Circuit codec rejects (Ultra only - Mega delegates on-curve check to ECCVM)
519 [[maybe_unused]] auto point = Codec::template deserialize_from_fields<bn254_element>(circuit_fields);
520 EXPECT_FALSE(CircuitChecker::check(builder));
521 }
522}
523
532TYPED_TEST(stdlib_field_conversion, BothCodecsAcceptCanonicalRejectAlias)
533{
534 using Builder = TypeParam;
535 using Codec = StdlibCodec<field_t<Builder>>;
537
538 constexpr uint64_t NUM_LIMB_BITS = 68;
539 constexpr uint64_t LOW_BITS = NUM_LIMB_BITS * 2; // 136
540 const uint256_t LOW_MASK = (uint256_t(1) << LOW_BITS) - 1;
541
542 auto split_to_limbs = [&](const uint256_t& value) -> std::pair<uint256_t, uint256_t> {
543 return { value & LOW_MASK, value >> LOW_BITS };
544 };
545
546 // Test 1: q - 1 is accepted (max canonical value)
547 {
548 const uint256_t value = bb::fq::modulus - 1;
549 const auto [low_limb, high_limb] = split_to_limbs(value);
550
551 // Native codec: accepts
552 std::vector<bb::fr> native_fields = { bb::fr(low_limb), bb::fr(high_limb) };
553 auto native_result = FrCodec::deserialize_from_fields<bb::fq>(native_fields);
554 EXPECT_EQ(uint256_t(native_result), value);
555
556 // Circuit codec: accepts
560 [[maybe_unused]] auto circuit_result = Codec::template deserialize_from_fields<fq_ct>(circuit_fields);
561 EXPECT_TRUE(CircuitChecker::check(builder));
562 }
563
564 // Test 2: q is rejected (smallest alias)
565 {
567 const auto [low_limb, high_limb] = split_to_limbs(value);
568
569 // Native codec: rejects
570 std::vector<bb::fr> native_fields = { bb::fr(low_limb), bb::fr(high_limb) };
571 EXPECT_THROW(FrCodec::deserialize_from_fields<bb::fq>(native_fields), std::runtime_error);
572
573 // Circuit codec: rejects via assert_is_in_field
574 {
579 bb::fr(high_limb)) };
580 [[maybe_unused]] auto circuit_result = Codec::template deserialize_from_fields<fq_ct>(circuit_fields);
581 EXPECT_FALSE(CircuitChecker::check(builder));
582 }
583 }
584
585 // Test 3: Large value between q and 2^254 is rejected
586 {
587 const uint256_t value = (uint256_t(1) << 254) - 1; // 2^254 - 1, well above modulus
588 const auto [low_limb, high_limb] = split_to_limbs(value);
589
590 // Verify this is indeed between modulus and 2^254
591 EXPECT_GT(value, bb::fq::modulus);
592 EXPECT_LT(value, uint256_t(1) << 254);
593
594 // Native codec: rejects
595 std::vector<bb::fr> native_fields = { bb::fr(low_limb), bb::fr(high_limb) };
596 EXPECT_THROW(FrCodec::deserialize_from_fields<bb::fq>(native_fields), std::runtime_error);
597
598 // Circuit codec: rejects via assert_is_in_field
599 {
604 bb::fr(high_limb)) };
605 [[maybe_unused]] auto circuit_result = Codec::template deserialize_from_fields<fq_ct>(circuit_fields);
606 EXPECT_FALSE(CircuitChecker::check(builder));
607 }
608 }
609}
610
611} // namespace bb::stdlib::field_conversion_tests
#define EXPECT_THROW_WITH_MESSAGE(code, expectedMessageRegex)
Definition assert.hpp:193
#define BB_DISABLE_ASSERTS()
Definition assert.hpp:33
static bool check(const Builder &circuit)
Check the witness satisifies the circuit.
A univariate polynomial represented by its values on {0, 1,..., domain_end - 1}.
typename bb::g1 Group
Definition bn254.hpp:20
typename Group::affine_element AffineElement
Definition bn254.hpp:22
typename Group::affine_element AffineElement
Definition grumpkin.hpp:63
constexpr bool is_point_at_infinity() const noexcept
static std::vector< fr > serialize_to_fields(const T &val)
Core stdlib Transcript serialization method.
cycle_group represents a group Element of the proving system's embedded curve, i.e....
AffineElement get_value() const
void check_deserialization_gate_count(CreateFn create_native, uint32_t expected_gates, size_t num_elements=1)
Helper to test gate counts for deserialization.
static field_t from_witness(Builder *ctx, const bb::fr &input)
Definition field.hpp:466
AluTraceBuilder builder
Definition alu.test.cpp:124
bn254::BaseField fq_ct
testing::Types< UltraCircuitBuilder, MegaCircuitBuilder > BuilderTypes
element< Builder, fq< Builder >, fr< Builder >, curve::BN254::Group > bn254_element
void check_circuit_and_gate_count(Builder &builder, uint32_t expected_gates_without_base)
Utility function for gate count checking and circuit verification.
std::conditional_t< IsGoblinBigGroup< C, Fq, Fr, G >, element_goblin::goblin_element< C, goblin_field< C >, Fr, G >, element_default::element< C, Fq, Fr, G > > element
element wraps either element_default::element or element_goblin::goblin_element depending on parametr...
TYPED_TEST_SUITE(CommitmentKeyTest, Curves)
field< Bn254FrParams > fr
Definition fr.hpp:174
TYPED_TEST(CommitmentKeyTest, CommitToZeroPoly)
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
uint8_t len
StdlibCodec for in-circuit (recursive) verification transcript handling.
static constexpr uint256_t modulus
static field random_element(numeric::RNG *engine=nullptr) noexcept
static constexpr uint256_t modulus_minus_two