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
120TYPED_TEST(stdlib_field_conversion, ConvertShortChallenge)
121{
122 using Builder = TypeParam;
123 using Codec = StdlibCodec<field_t<Builder>>;
124 constexpr size_t SHORT_BITS = 2 * fq<Builder>::NUM_LIMB_BITS;
125 const uint256_t raw(std::string("9a807b615c4d3e2fa0b1c2d3e4f56789fedcba9876543210abcdef0123456789"));
126
128 const uint256_t val = raw.slice(0, SHORT_BITS);
129 const auto chal = fr<Builder>::from_witness(&builder, bb::fr(val));
130
131 EXPECT_EQ(uint256_t(Codec::template convert_short_challenge<fr<Builder>>(chal).get_value()), val);
132 EXPECT_EQ(Codec::template convert_short_challenge<fq<Builder>>(chal).get_value(), uint512_t(val));
133 EXPECT_TRUE(CircuitChecker::check(builder));
134}
135
142{
143 using Builder = TypeParam;
144 using Codec = StdlibCodec<field_t<Builder>>;
145
147 const bb::fr val = bb::fr::random_element();
148 const auto chal = fr<Builder>::from_witness(&builder, val);
149
150 EXPECT_EQ(uint256_t(Codec::template convert_full_challenge<fr<Builder>>(chal).get_value()), uint256_t(val));
151 EXPECT_EQ(Codec::template convert_full_challenge<fq<Builder>>(chal).get_value(), uint512_t(uint256_t(val)));
152 EXPECT_TRUE(CircuitChecker::check(builder));
153}
154
159TYPED_TEST(stdlib_field_conversion, FieldConversionBN254AffineElement)
160{
161 using Builder = TypeParam;
162 { // Serialize and deserialize the bn254 generator
164
165 bn254_element<Builder> group_element =
166 bn254_element<Builder>::from_witness(&builder, curve::BN254::AffineElement::one());
167 this->check_conversion(group_element);
168 }
169 { // Serialize and deserialize a valid bn254 point
171
173 bn254_element<Builder> group_element = bn254_element<Builder>::from_witness(&builder, group_element_val);
174 this->check_conversion(group_element);
175 }
176
177 { // Serialize and deserialize random Grumpkin points
179 const size_t num_points = 50;
180 const curve::BN254::AffineElement native_generator = curve::BN254::AffineElement::one();
181
182 for (size_t i = 0; i < num_points; i++) {
183 bb::fr random_scalar = bb::fr::random_element();
184 bn254_element<Builder> group_element =
185 bn254_element<Builder>::from_witness(&builder, native_generator * random_scalar);
186 this->check_conversion(group_element);
187 }
188 }
189 { // Serialize and deserialize the point at infinity (canonical (0,0) form)
191
192 bn254_element<Builder> group_element =
193 bn254_element<Builder>::from_witness(&builder, curve::BN254::AffineElement::infinity());
194 this->check_conversion(group_element);
195 }
196
197 { // Serialize and deserialize "coordinates" that do not correspond to any point on the curve
199
200 curve::BN254::AffineElement group_element_val(1, 4);
201 bn254_element<Builder> group_element;
204 "");
205 } else {
206 group_element = bn254_element<Builder>::from_witness(&builder, group_element_val);
207 this->check_conversion(group_element);
208 }
209 }
210}
211
216TYPED_TEST(stdlib_field_conversion, FieldConversionGrumpkinAffineElement)
217{
218 using Builder = TypeParam;
219
220 { // Serialize and deserialize the Grumpkin generator
222 grumpkin_element<Builder> group_element =
223 grumpkin_element<Builder>::from_witness(&builder, curve::Grumpkin::AffineElement::one());
224 this->check_conversion(group_element);
225 }
226 { // Serialize and deserialize random Grumpkin points
228 const size_t num_points = 50;
229 const curve::Grumpkin::AffineElement native_generator = curve::Grumpkin::AffineElement::one();
230
231 for (size_t i = 0; i < num_points; i++) {
232 bb::fq random_scalar = bb::fq::random_element();
233 grumpkin_element<Builder> group_element =
234 grumpkin_element<Builder>::from_witness(&builder, native_generator * random_scalar);
235 this->check_conversion(group_element);
236 }
237 }
238
239 { // Serialize and deserialize "coordinates" that do not correspond to any point on the curve
240 BB_DISABLE_ASSERTS(); // Avoid on_curve assertion failure in cycle_group constructor
242
243 curve::Grumpkin::AffineElement group_element_val(12, 100);
245 this->check_conversion(group_element, /* valid circuit */ false);
246 }
247
248 { // Serialize and deserialize the point at infinity
250
251 // from_witness handles infinity: coordinates are set to (0,0), and the 2-arg constructor
252 // auto-detects infinity from x^2 + 5*y^2 == 0.
253 grumpkin_element<Builder> group_element =
254 grumpkin_element<Builder>::from_witness(&builder, curve::Grumpkin::AffineElement::infinity());
255 this->check_conversion(group_element);
256 }
257}
258
259TYPED_TEST(stdlib_field_conversion, DeserializePointAtInfinity)
260{
261 using Builder = TypeParam;
262 using Codec = StdlibCodec<field_t<Builder>>;
265
266 {
267 std::vector<fr<Builder>> zeros(4, zero);
268
269 bn254_element<Builder> point_at_infinity =
270 Codec::template deserialize_from_fields<bn254_element<Builder>>(zeros);
271
272 EXPECT_TRUE(point_at_infinity.get_value().is_point_at_infinity());
273 EXPECT_TRUE(CircuitChecker::check(builder));
274 }
275 {
276 std::vector<fr<Builder>> zeros(2, zero);
277
278 grumpkin_element<Builder> point_at_infinity =
279 Codec::template deserialize_from_fields<grumpkin_element<Builder>>(zeros);
280
281 EXPECT_TRUE(point_at_infinity.get_value().is_point_at_infinity());
282 EXPECT_TRUE(CircuitChecker::check(builder));
283 }
284}
285
289TYPED_TEST(stdlib_field_conversion, FieldConversionArrayBn254Fr)
290{
291 using Builder = TypeParam;
293
294 // Constructing std::array objects with fr<Builder> values
295 std::array<fr<Builder>, 4> array_of_frs_4{
297 };
298 this->check_conversion_iterable(array_of_frs_4);
299
302 fr<Builder>(&builder, 215215125),
303 fr<Builder>(&builder, 102701750),
304 fr<Builder>(&builder, 367032),
305 fr<Builder>(&builder, 12985028),
307 this->check_conversion_iterable(array_of_frs_7);
308}
309
313TYPED_TEST(stdlib_field_conversion, FieldConversionArrayGrumpkinFr)
314{
315 using Builder = TypeParam;
317
318 // Constructing std::array objects with fq<Builder> values
319 std::array<fq<Builder>, 4> array_of_fqs_4{
321 &builder,
322 static_cast<bb::fq>(std::string("9a807b615c4d3e2fa0b1c2d3e4f56789fedcba9876543210abcdef0123456789"))),
324 &builder,
325 static_cast<bb::fq>(std::string("2bf1eaf87f7d27e8dc4056e9af975985bccc89077a21891d6c7b6ccce0631f95"))),
327 &builder,
328 static_cast<bb::fq>(std::string("9a807b615c4d3e2fa0b1c2d3e4f56789fedcba9876543210abcdef0123456789"))),
330 &builder,
331 static_cast<bb::fq>(std::string("018555a8eb50cf07f64b019ebaf3af3c925c93e631f3ecd455db07bbb52bbdd3"))),
332 };
333 this->check_conversion_iterable(array_of_fqs_4);
334}
335
339TYPED_TEST(stdlib_field_conversion, FieldConversionUnivariateBn254Fr)
340{
341 using Builder = TypeParam;
343
344 // Constructing Univariate objects with fr<Builder> values
345 Univariate<fr<Builder>, 4> univariate{
347 };
348 this->check_conversion_iterable(univariate);
349}
350
354TYPED_TEST(stdlib_field_conversion, FieldConversionUnivariateGrumpkinFr)
355{
356 using Builder = TypeParam;
358
359 // Constructing std::array objects with fq<Builder> values
360 Univariate<fq<Builder>, 4> univariate{
362 &builder,
363 static_cast<bb::fq>(std::string("9a807b615c4d3e2fa0b1c2d3e4f56789fedcba9876543210abcdef0123456789"))),
365 &builder,
366 static_cast<bb::fq>(std::string("2bf1eaf87f7d27e8dc4056e9af975985bccc89077a21891d6c7b6ccce0631f95"))),
368 &builder,
369 static_cast<bb::fq>(std::string("018555a8eb50cf07f64b019ebaf3af3c925c93e631f3ecd455db07bbb52bbdd3"))),
371 &builder,
372 static_cast<bb::fq>(std::string("2bf1eaf87f7d27e8dc4056e9af975985bccc89077a21891d6c7b6ccce0631f95"))) }
373 };
374 this->check_conversion_iterable(univariate);
375}
376
377// ============================================================================
378// Gate Count Tests for Deserialization Operations
379// ============================================================================
380
385TYPED_TEST(stdlib_field_conversion, GateCountScalarDeserialization)
386{
387 // Scalar deserialization adds no gates (just witness creation)
388 this->template check_deserialization_gate_count<fr<TypeParam>>([] { return bb::fr::random_element(); }, 0);
389}
390
394TYPED_TEST(stdlib_field_conversion, GateCountBigfieldDeserialization)
395{
396 // Deserializing a single bigfield element is expensive due to creating new ranges for range constraints
397 this->template check_deserialization_gate_count<fq<TypeParam>>([] { return bb::fq::random_element(); }, 3513);
398}
399
404TYPED_TEST(stdlib_field_conversion, GateCountMultipleBigfieldDeserialization)
405{
406 this->template check_deserialization_gate_count<fq<TypeParam>>([] { return bb::fq::random_element(); }, 3913, 10);
407}
408
413TYPED_TEST(stdlib_field_conversion, GateCountBN254PointDeserialization)
414{
415 using Builder = TypeParam;
416 // Ultra: full bigfield construction + on-curve validation + assert_is_in_field for x and y
417 // Mega: no in-circuit checks; range constraint and on_curve validation deferred to ECCVM and Translator
418 constexpr uint32_t expected = std::is_same_v<Builder, bb::UltraCircuitBuilder> ? 3865 : 0;
419 this->template check_deserialization_gate_count<bn254_element<Builder>>(
420 [] { return curve::BN254::AffineElement::random_element(); }, expected);
421}
422
426TYPED_TEST(stdlib_field_conversion, GateCountMultipleBN254PointDeserialization)
427{
428 using Builder = TypeParam;
429
430 constexpr uint32_t expected = std::is_same_v<Builder, bb::UltraCircuitBuilder> ? 5746 : 0;
431 this->template check_deserialization_gate_count<bn254_element<Builder>>(
432 [] { return curve::BN254::AffineElement::random_element(); }, expected, 10);
433}
434
439TYPED_TEST(stdlib_field_conversion, GateCountGrumpkinPointDeserialization)
440{
441 this->template check_deserialization_gate_count<grumpkin_element<TypeParam>>(
442 [] { return curve::Grumpkin::AffineElement::random_element(); }, 10);
443}
444
449TYPED_TEST(stdlib_field_conversion, GateCountArrayDeserialization)
450{
451 constexpr size_t SIZE = 8;
452 this->template check_deserialization_gate_count<std::array<fr<TypeParam>, SIZE>>(
453 [] {
455 for (size_t i = 0; i < SIZE; ++i) {
456 arr[i] = bb::fr::random_element();
457 }
458 return arr;
459 },
460 0);
461}
462
467TYPED_TEST(stdlib_field_conversion, GateCountUnivariateDeserialization)
468{
469 constexpr size_t LENGTH = 8;
470 this->template check_deserialization_gate_count<Univariate<fr<TypeParam>, LENGTH>>(
471 [] {
473 for (size_t i = 0; i < LENGTH; ++i) {
474 evals[i] = bb::fr::random_element();
475 }
476 return Univariate<bb::fr, LENGTH>(evals);
477 },
478 0);
479}
480
487TYPED_TEST(stdlib_field_conversion, BigfieldDeserializationFailsOnLimbOverflow)
488{
489 using Builder = TypeParam;
490 using Codec = StdlibCodec<field_t<Builder>>;
491
492 bb::fr low_limb = bb::fr(0);
493 // 2^136 placed in high_limb position far exceeds the 2^118 bound for high limbs
494 bb::fr high_limb = bb::fr(uint256_t(1) << (2 * fq<Builder>::NUM_LIMB_BITS));
495
496 // Test 1: Native codec should reject via BB_ASSERT (asserts enabled)
497 {
498 std::vector<bb::fr> native_fields = { low_limb, high_limb };
499 EXPECT_THROW(FrCodec::deserialize_from_fields<bb::fq>(native_fields), std::runtime_error);
500 }
501
502 // Test 2: Circuit codec should reject via circuit constraints (disable asserts to bypass bigfield constructor
503 // checks)
504 {
509
510 // Deserialize as bigfield - this creates the bigfield from the two limbs
511 [[maybe_unused]] auto bigfield_val = Codec::template deserialize_from_fields<fq<Builder>>(circuit_fields);
512
513 // Circuit should fail validation
514 EXPECT_FALSE(CircuitChecker::check(builder));
515 }
516}
517
518// ============================================================================
519// Codec Consistency Tests: Verify FrCodec and StdlibCodec behave identically
520// ============================================================================
521
528TYPED_TEST(stdlib_field_conversion, BothCodecsRejectPointAtInfinityAlias)
529{
530 using Builder = TypeParam;
531 using Codec = StdlibCodec<field_t<Builder>>;
534
535 constexpr uint64_t NUM_LIMB_BITS = 68;
536 const uint256_t modulus = bb::fq::modulus;
537
538 // Create alias coordinates: x = modulus, y = modulus
539 const uint256_t x_lo = modulus & ((uint256_t(1) << (NUM_LIMB_BITS * 2)) - 1);
540 const uint256_t x_hi = modulus >> (NUM_LIMB_BITS * 2);
541
542 // Test 1: Native codec rejects via on_curve check
543 {
544 std::vector<bb::fr> native_fields = { bb::fr(x_lo), bb::fr(x_hi), bb::fr(x_lo), bb::fr(x_hi) };
545 EXPECT_THROW(FrCodec::deserialize_from_fields<curve::BN254::AffineElement>(native_fields), std::runtime_error);
546 }
547
548 // Test 2: Circuit codec rejects (Ultra only - Mega delegates on-curve check to ECCVM)
556 [[maybe_unused]] auto point = Codec::template deserialize_from_fields<bn254_element>(circuit_fields);
557 EXPECT_FALSE(CircuitChecker::check(builder));
558 }
559}
560
569TYPED_TEST(stdlib_field_conversion, BothCodecsAcceptCanonicalRejectAlias)
570{
571 using Builder = TypeParam;
572 using Codec = StdlibCodec<field_t<Builder>>;
574
575 constexpr uint64_t NUM_LIMB_BITS = 68;
576 constexpr uint64_t LOW_BITS = NUM_LIMB_BITS * 2; // 136
577 const uint256_t LOW_MASK = (uint256_t(1) << LOW_BITS) - 1;
578
579 auto split_to_limbs = [&](const uint256_t& value) -> std::pair<uint256_t, uint256_t> {
580 return { value & LOW_MASK, value >> LOW_BITS };
581 };
582
583 // Test 1: q - 1 is accepted (max canonical value)
584 {
585 const uint256_t value = bb::fq::modulus - 1;
586 const auto [low_limb, high_limb] = split_to_limbs(value);
587
588 // Native codec: accepts
589 std::vector<bb::fr> native_fields = { bb::fr(low_limb), bb::fr(high_limb) };
590 auto native_result = FrCodec::deserialize_from_fields<bb::fq>(native_fields);
591 EXPECT_EQ(uint256_t(native_result), value);
592
593 // Circuit codec: accepts
597 [[maybe_unused]] auto circuit_result = Codec::template deserialize_from_fields<fq_ct>(circuit_fields);
598 EXPECT_TRUE(CircuitChecker::check(builder));
599 }
600
601 // Test 2: q is rejected (smallest alias)
602 {
604 const auto [low_limb, high_limb] = split_to_limbs(value);
605
606 // Native codec: rejects
607 std::vector<bb::fr> native_fields = { bb::fr(low_limb), bb::fr(high_limb) };
608 EXPECT_THROW(FrCodec::deserialize_from_fields<bb::fq>(native_fields), std::runtime_error);
609
610 // Circuit codec: rejects via assert_is_in_field
611 {
616 bb::fr(high_limb)) };
617 [[maybe_unused]] auto circuit_result = Codec::template deserialize_from_fields<fq_ct>(circuit_fields);
618 EXPECT_FALSE(CircuitChecker::check(builder));
619 }
620 }
621
622 // Test 3: Large value between q and 2^254 is rejected
623 {
624 const uint256_t value = (uint256_t(1) << 254) - 1; // 2^254 - 1, well above modulus
625 const auto [low_limb, high_limb] = split_to_limbs(value);
626
627 // Verify this is indeed between modulus and 2^254
628 EXPECT_GT(value, bb::fq::modulus);
629 EXPECT_LT(value, uint256_t(1) << 254);
630
631 // Native codec: rejects
632 std::vector<bb::fr> native_fields = { bb::fr(low_limb), bb::fr(high_limb) };
633 EXPECT_THROW(FrCodec::deserialize_from_fields<bb::fq>(native_fields), std::runtime_error);
634
635 // Circuit codec: rejects via assert_is_in_field
636 {
641 bb::fr(high_limb)) };
642 [[maybe_unused]] auto circuit_result = Codec::template deserialize_from_fields<fq_ct>(circuit_fields);
643 EXPECT_FALSE(CircuitChecker::check(builder));
644 }
645 }
646}
647
648} // namespace bb::stdlib::field_conversion_tests
#define EXPECT_THROW_WITH_MESSAGE(code, expectedMessageRegex)
Definition assert.hpp:224
#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:64
constexpr bool is_point_at_infinity() const noexcept
constexpr uint256_t slice(uint64_t start, uint64_t end) const
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:480
AluTraceBuilder builder
Definition alu.test.cpp:124
bn254::BaseField fq_ct
uintx< uint256_t > uint512_t
Definition uintx.hpp:309
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
bb::VectorAffineElementPushSpan< BaseParams > out
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