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, bool point_at_infinity = false)
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 bool expected = std::is_same_v<Builder, UltraCircuitBuilder> ? !point_at_infinity : true;
58
59 EXPECT_EQ(in.get_value() == out.get_value(), expected);
60
61 auto ctx = in.get_context();
62
63 EXPECT_EQ(CircuitChecker::check(*ctx), valid_circuit);
64 }
65
66 template <typename T> void check_conversion_iterable(T x)
67 {
68 size_t len = Codec::template calc_num_fields<T>();
69 auto frs = Codec::template serialize_to_fields<T>(x);
70 EXPECT_EQ(len, frs.size());
71 auto y = Codec::template deserialize_from_fields<T>(frs);
72 EXPECT_EQ(x.size(), y.size());
73 for (auto [val1, val2] : zip_view(x, y)) {
74 EXPECT_EQ(val1.get_value(), val2.get_value());
75 }
76 }
77};
78
79using BuilderTypes = testing::Types<UltraCircuitBuilder, MegaCircuitBuilder>;
80
82
87{
88 using Builder = TypeParam;
90 bb::fr field_element_val(
91 std::string("9a807b615c4d3e2fa0b1c2d3e4f56789fedcba9876543210abcdef0123456789")); // 256 bits
92 fr<Builder> field_element(&builder, field_element_val);
93 this->check_conversion(field_element);
94
95 field_element_val = bb::fr::modulus_minus_two; // modulus - 2
96 field_element = fr<Builder>(&builder, field_element_val);
97 this->check_conversion(field_element);
98
99 field_element_val = bb::fr(1);
100 field_element = fr<Builder>(&builder, field_element_val);
101 this->check_conversion(field_element);
102}
103
107TYPED_TEST(stdlib_field_conversion, FieldConversionGrumpkinFr)
108{
109 using Builder = TypeParam;
111
112 // Constructing bigfield objects with bb::fq values
113 bb::fq field_element_val(
114 std::string("9a807b615c4d3e2fa0b1c2d3e4f56789fedcba9876543210abcdef0123456789")); // 256 bits
115 this->check_conversion(fq<Builder>::from_witness(&builder, field_element_val));
116}
117
122TYPED_TEST(stdlib_field_conversion, FieldConversionBN254AffineElement)
123{
124 using Builder = TypeParam;
125 { // Serialize and deserialize the bn254 generator
127
128 bn254_element<Builder> group_element =
129 bn254_element<Builder>::from_witness(&builder, curve::BN254::AffineElement::one());
130 this->check_conversion(group_element);
131 }
132 { // Serialize and deserialize a valid bn254 point
134
136 bn254_element<Builder> group_element = bn254_element<Builder>::from_witness(&builder, group_element_val);
137 this->check_conversion(group_element);
138 }
139
140 { // Serialize and deserialize random Grumpkin points
142 const size_t num_points = 50;
143 const curve::BN254::AffineElement native_generator = curve::BN254::AffineElement::one();
144
145 for (size_t i = 0; i < num_points; i++) {
146 bb::fr random_scalar = bb::fr::random_element();
147 bn254_element<Builder> group_element =
148 bn254_element<Builder>::from_witness(&builder, native_generator * random_scalar);
149 this->check_conversion(group_element);
150 }
151 }
152 // TODO(https://github.com/AztecProtocol/barretenberg/issues/1527): Remove `point_at_infinity` flag when point at
153 // infinity is consistently represented.
154 { // Serialize and deserialize the point at infinity
156
157 bn254_element<Builder> group_element =
158 bn254_element<Builder>::from_witness(&builder, curve::BN254::AffineElement::infinity());
159 // The circuit is valid, because the point at infinity is set to `one`.
160 this->check_conversion(group_element, /* valid circuit */ true, /* point at infinity */ true);
161 }
162
163 { // Serialize and deserialize "coordinates" that do not correspond to any point on the curve
165
166 curve::BN254::AffineElement group_element_val(1, 4);
167 bn254_element<Builder> group_element;
170 "");
171 } else {
172 group_element = bn254_element<Builder>::from_witness(&builder, group_element_val);
173 this->check_conversion(group_element);
174 }
175 }
176}
177
182TYPED_TEST(stdlib_field_conversion, FieldConversionGrumpkinAffineElement)
183{
184 using Builder = TypeParam;
185
186 { // Serialize and deserialize the Grumpkin generator
188 grumpkin_element<Builder> group_element =
189 grumpkin_element<Builder>::from_witness(&builder, curve::Grumpkin::AffineElement::one());
190 this->check_conversion(group_element);
191 }
192 { // Serialize and deserialize random Grumpkin points
194 const size_t num_points = 50;
195 const curve::Grumpkin::AffineElement native_generator = curve::Grumpkin::AffineElement::one();
196
197 for (size_t i = 0; i < num_points; i++) {
198 bb::fq random_scalar = bb::fq::random_element();
199 grumpkin_element<Builder> group_element =
200 grumpkin_element<Builder>::from_witness(&builder, native_generator * random_scalar);
201 this->check_conversion(group_element);
202 }
203 }
204
205 { // Serialize and deserialize "coordinates" that do not correspond to any point on the curve
206 BB_DISABLE_ASSERTS(); // Avoid on_curve assertion failure in cycle_group constructor
208
209 curve::Grumpkin::AffineElement group_element_val(12, 100);
211 this->check_conversion(group_element, /* valid circuit */ false);
212 }
213
214 { // Serialize and deserialize the point at infinity
216
217 grumpkin_element<Builder> group_element =
218 grumpkin_element<Builder>::from_witness(&builder, curve::Grumpkin::AffineElement::infinity());
219 this->check_conversion(group_element);
220 }
221}
222
223TYPED_TEST(stdlib_field_conversion, DeserializePointAtInfinity)
224{
225 using Builder = TypeParam;
226 using Codec = StdlibCodec<field_t<Builder>>;
229
230 {
231 std::vector<fr<Builder>> zeros(4, zero);
232
233 bn254_element<Builder> point_at_infinity =
234 Codec::template deserialize_from_fields<bn254_element<Builder>>(zeros);
235
236 EXPECT_TRUE(point_at_infinity.is_point_at_infinity().get_value());
237 EXPECT_TRUE(CircuitChecker::check(builder));
238 }
239 {
240 std::vector<fr<Builder>> zeros(2, zero);
241
242 grumpkin_element<Builder> point_at_infinity =
243 Codec::template deserialize_from_fields<grumpkin_element<Builder>>(zeros);
244
245 EXPECT_TRUE(point_at_infinity.is_point_at_infinity().get_value());
246 EXPECT_TRUE(CircuitChecker::check(builder));
247 }
248}
249
253TYPED_TEST(stdlib_field_conversion, FieldConversionArrayBn254Fr)
254{
255 using Builder = TypeParam;
257
258 // Constructing std::array objects with fr<Builder> values
259 std::array<fr<Builder>, 4> array_of_frs_4{
261 };
262 this->check_conversion_iterable(array_of_frs_4);
263
266 fr<Builder>(&builder, 215215125),
267 fr<Builder>(&builder, 102701750),
268 fr<Builder>(&builder, 367032),
269 fr<Builder>(&builder, 12985028),
271 this->check_conversion_iterable(array_of_frs_7);
272}
273
277TYPED_TEST(stdlib_field_conversion, FieldConversionArrayGrumpkinFr)
278{
279 using Builder = TypeParam;
281
282 // Constructing std::array objects with fq<Builder> values
283 std::array<fq<Builder>, 4> array_of_fqs_4{
285 &builder,
286 static_cast<bb::fq>(std::string("9a807b615c4d3e2fa0b1c2d3e4f56789fedcba9876543210abcdef0123456789"))),
288 &builder,
289 static_cast<bb::fq>(std::string("2bf1eaf87f7d27e8dc4056e9af975985bccc89077a21891d6c7b6ccce0631f95"))),
291 &builder,
292 static_cast<bb::fq>(std::string("9a807b615c4d3e2fa0b1c2d3e4f56789fedcba9876543210abcdef0123456789"))),
294 &builder,
295 static_cast<bb::fq>(std::string("018555a8eb50cf07f64b019ebaf3af3c925c93e631f3ecd455db07bbb52bbdd3"))),
296 };
297 this->check_conversion_iterable(array_of_fqs_4);
298}
299
303TYPED_TEST(stdlib_field_conversion, FieldConversionUnivariateBn254Fr)
304{
305 using Builder = TypeParam;
307
308 // Constructing Univariate objects with fr<Builder> values
309 Univariate<fr<Builder>, 4> univariate{
311 };
312 this->check_conversion_iterable(univariate);
313}
314
318TYPED_TEST(stdlib_field_conversion, FieldConversionUnivariateGrumpkinFr)
319{
320 using Builder = TypeParam;
322
323 // Constructing std::array objects with fq<Builder> values
324 Univariate<fq<Builder>, 4> univariate{
326 &builder,
327 static_cast<bb::fq>(std::string("9a807b615c4d3e2fa0b1c2d3e4f56789fedcba9876543210abcdef0123456789"))),
329 &builder,
330 static_cast<bb::fq>(std::string("2bf1eaf87f7d27e8dc4056e9af975985bccc89077a21891d6c7b6ccce0631f95"))),
332 &builder,
333 static_cast<bb::fq>(std::string("018555a8eb50cf07f64b019ebaf3af3c925c93e631f3ecd455db07bbb52bbdd3"))),
335 &builder,
336 static_cast<bb::fq>(std::string("2bf1eaf87f7d27e8dc4056e9af975985bccc89077a21891d6c7b6ccce0631f95"))) }
337 };
338 this->check_conversion_iterable(univariate);
339}
340
341// ============================================================================
342// Gate Count Tests for Deserialization Operations
343// ============================================================================
344
349TYPED_TEST(stdlib_field_conversion, GateCountScalarDeserialization)
350{
351 // Scalar deserialization adds no gates (just witness creation)
352 this->template check_deserialization_gate_count<fr<TypeParam>>([] { return bb::fr::random_element(); }, 0);
353}
354
358TYPED_TEST(stdlib_field_conversion, GateCountBigfieldDeserialization)
359{
360 // Deserializing a single bigfield element is expensive due to creating new ranges for range constraints
361 this->template check_deserialization_gate_count<fq<TypeParam>>([] { return bb::fq::random_element(); }, 3515);
362}
363
368TYPED_TEST(stdlib_field_conversion, GateCountMultipleBigfieldDeserialization)
369{
370 this->template check_deserialization_gate_count<fq<TypeParam>>([] { return bb::fq::random_element(); }, 3914, 10);
371}
372
377TYPED_TEST(stdlib_field_conversion, GateCountBN254PointDeserialization)
378{
379 using Builder = TypeParam;
380 // Ultra: full bigfield construction + on-curve validation + assert_is_in_field for x and y
381 // Mega: only is_infinity check, range constraint and on_curve validation deferred to ECCVM and Translator
382 constexpr uint32_t expected = std::is_same_v<Builder, bb::UltraCircuitBuilder> ? 3850 : 5;
383 this->template check_deserialization_gate_count<bn254_element<Builder>>(
384 [] { return curve::BN254::AffineElement::random_element(); }, expected);
385}
386
390TYPED_TEST(stdlib_field_conversion, GateCountMultipleBN254PointDeserialization)
391{
392 using Builder = TypeParam;
393
394 constexpr uint32_t expected = std::is_same_v<Builder, bb::UltraCircuitBuilder> ? 5601 : 50;
395 this->template check_deserialization_gate_count<bn254_element<Builder>>(
396 [] { return curve::BN254::AffineElement::random_element(); }, expected, 10);
397}
398
403TYPED_TEST(stdlib_field_conversion, GateCountGrumpkinPointDeserialization)
404{
405 this->template check_deserialization_gate_count<grumpkin_element<TypeParam>>(
406 [] { return curve::Grumpkin::AffineElement::random_element(); }, 10);
407}
408
413TYPED_TEST(stdlib_field_conversion, GateCountArrayDeserialization)
414{
415 constexpr size_t SIZE = 8;
416 this->template check_deserialization_gate_count<std::array<fr<TypeParam>, SIZE>>(
417 [] {
419 for (size_t i = 0; i < SIZE; ++i) {
420 arr[i] = bb::fr::random_element();
421 }
422 return arr;
423 },
424 0);
425}
426
431TYPED_TEST(stdlib_field_conversion, GateCountUnivariateDeserialization)
432{
433 constexpr size_t LENGTH = 8;
434 this->template check_deserialization_gate_count<Univariate<fr<TypeParam>, LENGTH>>(
435 [] {
437 for (size_t i = 0; i < LENGTH; ++i) {
438 evals[i] = bb::fr::random_element();
439 }
440 return Univariate<bb::fr, LENGTH>(evals);
441 },
442 0);
443}
444
451TYPED_TEST(stdlib_field_conversion, BigfieldDeserializationFailsOnLimbOverflow)
452{
453 using Builder = TypeParam;
454 using Codec = StdlibCodec<field_t<Builder>>;
455
456 bb::fr low_limb = bb::fr(0);
457 // 2^136 placed in high_limb position far exceeds the 2^118 bound for high limbs
458 bb::fr high_limb = bb::fr(uint256_t(1) << (2 * fq<Builder>::NUM_LIMB_BITS));
459
460 // Test 1: Native codec should reject via BB_ASSERT (asserts enabled)
461 {
462 std::vector<bb::fr> native_fields = { low_limb, high_limb };
463 EXPECT_THROW(FrCodec::deserialize_from_fields<bb::fq>(native_fields), std::runtime_error);
464 }
465
466 // Test 2: Circuit codec should reject via circuit constraints (disable asserts to bypass bigfield constructor
467 // checks)
468 {
473
474 // Deserialize as bigfield - this creates the bigfield from the two limbs
475 [[maybe_unused]] auto bigfield_val = Codec::template deserialize_from_fields<fq<Builder>>(circuit_fields);
476
477 // Circuit should fail validation
478 EXPECT_FALSE(CircuitChecker::check(builder));
479 }
480}
481
482// ============================================================================
483// Codec Consistency Tests: Verify FrCodec and StdlibCodec behave identically
484// ============================================================================
485
492TYPED_TEST(stdlib_field_conversion, BothCodecsRejectPointAtInfinityAlias)
493{
494 using Builder = TypeParam;
495 using Codec = StdlibCodec<field_t<Builder>>;
498
499 constexpr uint64_t NUM_LIMB_BITS = 68;
500 const uint256_t modulus = bb::fq::modulus;
501
502 // Create alias coordinates: x = modulus, y = modulus
503 const uint256_t x_lo = modulus & ((uint256_t(1) << (NUM_LIMB_BITS * 2)) - 1);
504 const uint256_t x_hi = modulus >> (NUM_LIMB_BITS * 2);
505
506 // Test 1: Native codec rejects via on_curve check
507 {
508 std::vector<bb::fr> native_fields = { bb::fr(x_lo), bb::fr(x_hi), bb::fr(x_lo), bb::fr(x_hi) };
509 EXPECT_THROW(FrCodec::deserialize_from_fields<curve::BN254::AffineElement>(native_fields), std::runtime_error);
510 }
511
512 // Test 2: Circuit codec rejects (Ultra only - Mega delegates on-curve check to ECCVM)
520 [[maybe_unused]] auto point = Codec::template deserialize_from_fields<bn254_element>(circuit_fields);
521 EXPECT_FALSE(CircuitChecker::check(builder));
522 }
523}
524
533TYPED_TEST(stdlib_field_conversion, BothCodecsAcceptCanonicalRejectAlias)
534{
535 using Builder = TypeParam;
536 using Codec = StdlibCodec<field_t<Builder>>;
538
539 constexpr uint64_t NUM_LIMB_BITS = 68;
540 constexpr uint64_t LOW_BITS = NUM_LIMB_BITS * 2; // 136
541 const uint256_t LOW_MASK = (uint256_t(1) << LOW_BITS) - 1;
542
543 auto split_to_limbs = [&](const uint256_t& value) -> std::pair<uint256_t, uint256_t> {
544 return { value & LOW_MASK, value >> LOW_BITS };
545 };
546
547 // Test 1: q - 1 is accepted (max canonical value)
548 {
549 const uint256_t value = bb::fq::modulus - 1;
550 const auto [low_limb, high_limb] = split_to_limbs(value);
551
552 // Native codec: accepts
553 std::vector<bb::fr> native_fields = { bb::fr(low_limb), bb::fr(high_limb) };
554 auto native_result = FrCodec::deserialize_from_fields<bb::fq>(native_fields);
555 EXPECT_EQ(uint256_t(native_result), value);
556
557 // Circuit codec: accepts
561 [[maybe_unused]] auto circuit_result = Codec::template deserialize_from_fields<fq_ct>(circuit_fields);
562 EXPECT_TRUE(CircuitChecker::check(builder));
563 }
564
565 // Test 2: q is rejected (smallest alias)
566 {
568 const auto [low_limb, high_limb] = split_to_limbs(value);
569
570 // Native codec: rejects
571 std::vector<bb::fr> native_fields = { bb::fr(low_limb), bb::fr(high_limb) };
572 EXPECT_THROW(FrCodec::deserialize_from_fields<bb::fq>(native_fields), std::runtime_error);
573
574 // Circuit codec: rejects via assert_is_in_field
575 {
580 bb::fr(high_limb)) };
581 [[maybe_unused]] auto circuit_result = Codec::template deserialize_from_fields<fq_ct>(circuit_fields);
582 EXPECT_FALSE(CircuitChecker::check(builder));
583 }
584 }
585
586 // Test 3: Large value between q and 2^254 is rejected
587 {
588 const uint256_t value = (uint256_t(1) << 254) - 1; // 2^254 - 1, well above modulus
589 const auto [low_limb, high_limb] = split_to_limbs(value);
590
591 // Verify this is indeed between modulus and 2^254
592 EXPECT_GT(value, bb::fq::modulus);
593 EXPECT_LT(value, uint256_t(1) << 254);
594
595 // Native codec: rejects
596 std::vector<bb::fr> native_fields = { bb::fr(low_limb), bb::fr(high_limb) };
597 EXPECT_THROW(FrCodec::deserialize_from_fields<bb::fq>(native_fields), std::runtime_error);
598
599 // Circuit codec: rejects via assert_is_in_field
600 {
605 bb::fr(high_limb)) };
606 [[maybe_unused]] auto circuit_result = Codec::template deserialize_from_fields<fq_ct>(circuit_fields);
607 EXPECT_FALSE(CircuitChecker::check(builder));
608 }
609 }
610}
611
612} // 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
static std::vector< fr > serialize_to_fields(const T &val)
Core stdlib Transcript serialization method.
bool get_value() const
Definition bool.hpp:125
cycle_group represents a group Element of the proving system's embedded curve, i.e....
bool_t is_point_at_infinity() const
void check_conversion(T in, bool valid_circuit=true, bool point_at_infinity=false)
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...
Definition biggroup.hpp:978
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