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 (canonical (0,0) form)
153
154 bn254_element<Builder> group_element =
155 bn254_element<Builder>::from_witness(&builder, curve::BN254::AffineElement::infinity());
156 this->check_conversion(group_element);
157 }
158
159 { // Serialize and deserialize "coordinates" that do not correspond to any point on the curve
161
162 curve::BN254::AffineElement group_element_val(1, 4);
163 bn254_element<Builder> group_element;
166 "");
167 } else {
168 group_element = bn254_element<Builder>::from_witness(&builder, group_element_val);
169 this->check_conversion(group_element);
170 }
171 }
172}
173
178TYPED_TEST(stdlib_field_conversion, FieldConversionGrumpkinAffineElement)
179{
180 using Builder = TypeParam;
181
182 { // Serialize and deserialize the Grumpkin generator
184 grumpkin_element<Builder> group_element =
185 grumpkin_element<Builder>::from_witness(&builder, curve::Grumpkin::AffineElement::one());
186 this->check_conversion(group_element);
187 }
188 { // Serialize and deserialize random Grumpkin points
190 const size_t num_points = 50;
191 const curve::Grumpkin::AffineElement native_generator = curve::Grumpkin::AffineElement::one();
192
193 for (size_t i = 0; i < num_points; i++) {
194 bb::fq random_scalar = bb::fq::random_element();
195 grumpkin_element<Builder> group_element =
196 grumpkin_element<Builder>::from_witness(&builder, native_generator * random_scalar);
197 this->check_conversion(group_element);
198 }
199 }
200
201 { // Serialize and deserialize "coordinates" that do not correspond to any point on the curve
202 BB_DISABLE_ASSERTS(); // Avoid on_curve assertion failure in cycle_group constructor
204
205 curve::Grumpkin::AffineElement group_element_val(12, 100);
207 this->check_conversion(group_element, /* valid circuit */ false);
208 }
209
210 { // Serialize and deserialize the point at infinity
212
213 // from_witness handles infinity: coordinates are set to (0,0), and the 2-arg constructor
214 // auto-detects infinity from x^2 + 5*y^2 == 0.
215 grumpkin_element<Builder> group_element =
216 grumpkin_element<Builder>::from_witness(&builder, curve::Grumpkin::AffineElement::infinity());
217 this->check_conversion(group_element);
218 }
219}
220
221TYPED_TEST(stdlib_field_conversion, DeserializePointAtInfinity)
222{
223 using Builder = TypeParam;
224 using Codec = StdlibCodec<field_t<Builder>>;
227
228 {
229 std::vector<fr<Builder>> zeros(4, zero);
230
231 bn254_element<Builder> point_at_infinity =
232 Codec::template deserialize_from_fields<bn254_element<Builder>>(zeros);
233
234 EXPECT_TRUE(point_at_infinity.get_value().is_point_at_infinity());
235 EXPECT_TRUE(CircuitChecker::check(builder));
236 }
237 {
238 std::vector<fr<Builder>> zeros(2, zero);
239
240 grumpkin_element<Builder> point_at_infinity =
241 Codec::template deserialize_from_fields<grumpkin_element<Builder>>(zeros);
242
243 EXPECT_TRUE(point_at_infinity.get_value().is_point_at_infinity());
244 EXPECT_TRUE(CircuitChecker::check(builder));
245 }
246}
247
251TYPED_TEST(stdlib_field_conversion, FieldConversionArrayBn254Fr)
252{
253 using Builder = TypeParam;
255
256 // Constructing std::array objects with fr<Builder> values
257 std::array<fr<Builder>, 4> array_of_frs_4{
259 };
260 this->check_conversion_iterable(array_of_frs_4);
261
264 fr<Builder>(&builder, 215215125),
265 fr<Builder>(&builder, 102701750),
266 fr<Builder>(&builder, 367032),
267 fr<Builder>(&builder, 12985028),
269 this->check_conversion_iterable(array_of_frs_7);
270}
271
275TYPED_TEST(stdlib_field_conversion, FieldConversionArrayGrumpkinFr)
276{
277 using Builder = TypeParam;
279
280 // Constructing std::array objects with fq<Builder> values
281 std::array<fq<Builder>, 4> array_of_fqs_4{
283 &builder,
284 static_cast<bb::fq>(std::string("9a807b615c4d3e2fa0b1c2d3e4f56789fedcba9876543210abcdef0123456789"))),
286 &builder,
287 static_cast<bb::fq>(std::string("2bf1eaf87f7d27e8dc4056e9af975985bccc89077a21891d6c7b6ccce0631f95"))),
289 &builder,
290 static_cast<bb::fq>(std::string("9a807b615c4d3e2fa0b1c2d3e4f56789fedcba9876543210abcdef0123456789"))),
292 &builder,
293 static_cast<bb::fq>(std::string("018555a8eb50cf07f64b019ebaf3af3c925c93e631f3ecd455db07bbb52bbdd3"))),
294 };
295 this->check_conversion_iterable(array_of_fqs_4);
296}
297
301TYPED_TEST(stdlib_field_conversion, FieldConversionUnivariateBn254Fr)
302{
303 using Builder = TypeParam;
305
306 // Constructing Univariate objects with fr<Builder> values
307 Univariate<fr<Builder>, 4> univariate{
309 };
310 this->check_conversion_iterable(univariate);
311}
312
316TYPED_TEST(stdlib_field_conversion, FieldConversionUnivariateGrumpkinFr)
317{
318 using Builder = TypeParam;
320
321 // Constructing std::array objects with fq<Builder> values
322 Univariate<fq<Builder>, 4> univariate{
324 &builder,
325 static_cast<bb::fq>(std::string("9a807b615c4d3e2fa0b1c2d3e4f56789fedcba9876543210abcdef0123456789"))),
327 &builder,
328 static_cast<bb::fq>(std::string("2bf1eaf87f7d27e8dc4056e9af975985bccc89077a21891d6c7b6ccce0631f95"))),
330 &builder,
331 static_cast<bb::fq>(std::string("018555a8eb50cf07f64b019ebaf3af3c925c93e631f3ecd455db07bbb52bbdd3"))),
333 &builder,
334 static_cast<bb::fq>(std::string("2bf1eaf87f7d27e8dc4056e9af975985bccc89077a21891d6c7b6ccce0631f95"))) }
335 };
336 this->check_conversion_iterable(univariate);
337}
338
339// ============================================================================
340// Gate Count Tests for Deserialization Operations
341// ============================================================================
342
347TYPED_TEST(stdlib_field_conversion, GateCountScalarDeserialization)
348{
349 // Scalar deserialization adds no gates (just witness creation)
350 this->template check_deserialization_gate_count<fr<TypeParam>>([] { return bb::fr::random_element(); }, 0);
351}
352
356TYPED_TEST(stdlib_field_conversion, GateCountBigfieldDeserialization)
357{
358 // Deserializing a single bigfield element is expensive due to creating new ranges for range constraints
359 this->template check_deserialization_gate_count<fq<TypeParam>>([] { return bb::fq::random_element(); }, 3515);
360}
361
366TYPED_TEST(stdlib_field_conversion, GateCountMultipleBigfieldDeserialization)
367{
368 this->template check_deserialization_gate_count<fq<TypeParam>>([] { return bb::fq::random_element(); }, 3914, 10);
369}
370
375TYPED_TEST(stdlib_field_conversion, GateCountBN254PointDeserialization)
376{
377 using Builder = TypeParam;
378 // Ultra: full bigfield construction + on-curve validation + assert_is_in_field for x and y
379 // Mega: no in-circuit checks; range constraint and on_curve validation deferred to ECCVM and Translator
380 constexpr uint32_t expected = std::is_same_v<Builder, bb::UltraCircuitBuilder> ? 3865 : 0;
381 this->template check_deserialization_gate_count<bn254_element<Builder>>(
382 [] { return curve::BN254::AffineElement::random_element(); }, expected);
383}
384
388TYPED_TEST(stdlib_field_conversion, GateCountMultipleBN254PointDeserialization)
389{
390 using Builder = TypeParam;
391
392 constexpr uint32_t expected = std::is_same_v<Builder, bb::UltraCircuitBuilder> ? 5751 : 0;
393 this->template check_deserialization_gate_count<bn254_element<Builder>>(
394 [] { return curve::BN254::AffineElement::random_element(); }, expected, 10);
395}
396
401TYPED_TEST(stdlib_field_conversion, GateCountGrumpkinPointDeserialization)
402{
403 this->template check_deserialization_gate_count<grumpkin_element<TypeParam>>(
404 [] { return curve::Grumpkin::AffineElement::random_element(); }, 10);
405}
406
411TYPED_TEST(stdlib_field_conversion, GateCountArrayDeserialization)
412{
413 constexpr size_t SIZE = 8;
414 this->template check_deserialization_gate_count<std::array<fr<TypeParam>, SIZE>>(
415 [] {
417 for (size_t i = 0; i < SIZE; ++i) {
418 arr[i] = bb::fr::random_element();
419 }
420 return arr;
421 },
422 0);
423}
424
429TYPED_TEST(stdlib_field_conversion, GateCountUnivariateDeserialization)
430{
431 constexpr size_t LENGTH = 8;
432 this->template check_deserialization_gate_count<Univariate<fr<TypeParam>, LENGTH>>(
433 [] {
435 for (size_t i = 0; i < LENGTH; ++i) {
436 evals[i] = bb::fr::random_element();
437 }
438 return Univariate<bb::fr, LENGTH>(evals);
439 },
440 0);
441}
442
449TYPED_TEST(stdlib_field_conversion, BigfieldDeserializationFailsOnLimbOverflow)
450{
451 using Builder = TypeParam;
452 using Codec = StdlibCodec<field_t<Builder>>;
453
454 bb::fr low_limb = bb::fr(0);
455 // 2^136 placed in high_limb position far exceeds the 2^118 bound for high limbs
456 bb::fr high_limb = bb::fr(uint256_t(1) << (2 * fq<Builder>::NUM_LIMB_BITS));
457
458 // Test 1: Native codec should reject via BB_ASSERT (asserts enabled)
459 {
460 std::vector<bb::fr> native_fields = { low_limb, high_limb };
461 EXPECT_THROW(FrCodec::deserialize_from_fields<bb::fq>(native_fields), std::runtime_error);
462 }
463
464 // Test 2: Circuit codec should reject via circuit constraints (disable asserts to bypass bigfield constructor
465 // checks)
466 {
471
472 // Deserialize as bigfield - this creates the bigfield from the two limbs
473 [[maybe_unused]] auto bigfield_val = Codec::template deserialize_from_fields<fq<Builder>>(circuit_fields);
474
475 // Circuit should fail validation
476 EXPECT_FALSE(CircuitChecker::check(builder));
477 }
478}
479
480// ============================================================================
481// Codec Consistency Tests: Verify FrCodec and StdlibCodec behave identically
482// ============================================================================
483
490TYPED_TEST(stdlib_field_conversion, BothCodecsRejectPointAtInfinityAlias)
491{
492 using Builder = TypeParam;
493 using Codec = StdlibCodec<field_t<Builder>>;
496
497 constexpr uint64_t NUM_LIMB_BITS = 68;
498 const uint256_t modulus = bb::fq::modulus;
499
500 // Create alias coordinates: x = modulus, y = modulus
501 const uint256_t x_lo = modulus & ((uint256_t(1) << (NUM_LIMB_BITS * 2)) - 1);
502 const uint256_t x_hi = modulus >> (NUM_LIMB_BITS * 2);
503
504 // Test 1: Native codec rejects via on_curve check
505 {
506 std::vector<bb::fr> native_fields = { bb::fr(x_lo), bb::fr(x_hi), bb::fr(x_lo), bb::fr(x_hi) };
507 EXPECT_THROW(FrCodec::deserialize_from_fields<curve::BN254::AffineElement>(native_fields), std::runtime_error);
508 }
509
510 // Test 2: Circuit codec rejects (Ultra only - Mega delegates on-curve check to ECCVM)
518 [[maybe_unused]] auto point = Codec::template deserialize_from_fields<bn254_element>(circuit_fields);
519 EXPECT_FALSE(CircuitChecker::check(builder));
520 }
521}
522
531TYPED_TEST(stdlib_field_conversion, BothCodecsAcceptCanonicalRejectAlias)
532{
533 using Builder = TypeParam;
534 using Codec = StdlibCodec<field_t<Builder>>;
536
537 constexpr uint64_t NUM_LIMB_BITS = 68;
538 constexpr uint64_t LOW_BITS = NUM_LIMB_BITS * 2; // 136
539 const uint256_t LOW_MASK = (uint256_t(1) << LOW_BITS) - 1;
540
541 auto split_to_limbs = [&](const uint256_t& value) -> std::pair<uint256_t, uint256_t> {
542 return { value & LOW_MASK, value >> LOW_BITS };
543 };
544
545 // Test 1: q - 1 is accepted (max canonical value)
546 {
547 const uint256_t value = bb::fq::modulus - 1;
548 const auto [low_limb, high_limb] = split_to_limbs(value);
549
550 // Native codec: accepts
551 std::vector<bb::fr> native_fields = { bb::fr(low_limb), bb::fr(high_limb) };
552 auto native_result = FrCodec::deserialize_from_fields<bb::fq>(native_fields);
553 EXPECT_EQ(uint256_t(native_result), value);
554
555 // Circuit codec: accepts
559 [[maybe_unused]] auto circuit_result = Codec::template deserialize_from_fields<fq_ct>(circuit_fields);
560 EXPECT_TRUE(CircuitChecker::check(builder));
561 }
562
563 // Test 2: q is rejected (smallest alias)
564 {
566 const auto [low_limb, high_limb] = split_to_limbs(value);
567
568 // Native codec: rejects
569 std::vector<bb::fr> native_fields = { bb::fr(low_limb), bb::fr(high_limb) };
570 EXPECT_THROW(FrCodec::deserialize_from_fields<bb::fq>(native_fields), std::runtime_error);
571
572 // Circuit codec: rejects via assert_is_in_field
573 {
578 bb::fr(high_limb)) };
579 [[maybe_unused]] auto circuit_result = Codec::template deserialize_from_fields<fq_ct>(circuit_fields);
580 EXPECT_FALSE(CircuitChecker::check(builder));
581 }
582 }
583
584 // Test 3: Large value between q and 2^254 is rejected
585 {
586 const uint256_t value = (uint256_t(1) << 254) - 1; // 2^254 - 1, well above modulus
587 const auto [low_limb, high_limb] = split_to_limbs(value);
588
589 // Verify this is indeed between modulus and 2^254
590 EXPECT_GT(value, bb::fq::modulus);
591 EXPECT_LT(value, uint256_t(1) << 254);
592
593 // Native codec: rejects
594 std::vector<bb::fr> native_fields = { bb::fr(low_limb), bb::fr(high_limb) };
595 EXPECT_THROW(FrCodec::deserialize_from_fields<bb::fq>(native_fields), std::runtime_error);
596
597 // Circuit codec: rejects via assert_is_in_field
598 {
603 bb::fr(high_limb)) };
604 [[maybe_unused]] auto circuit_result = Codec::template deserialize_from_fields<fq_ct>(circuit_fields);
605 EXPECT_FALSE(CircuitChecker::check(builder));
606 }
607 }
608}
609
610} // 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:65
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:155
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