Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
affine_element.test.cpp
Go to the documentation of this file.
2
13
14#include "gmock/gmock.h"
15#include <algorithm>
16#include <fstream>
17#include <gtest/gtest.h>
18#include <iterator>
19#include <tuple>
20
21using ::testing::Each;
22using ::testing::ElementsAreArray;
23using ::testing::Eq;
24using ::testing::Property;
25
26using namespace bb;
27
28namespace {
29template <typename G1> class TestAffineElement : public testing::Test {
30 using element = typename G1::element;
31 using affine_element = typename G1::affine_element;
32 using Fr = typename G1::Fr;
33
34 public:
35 static void test_read_write_buffer()
36 {
37 // a generic point
38 {
39 affine_element P = affine_element(element::random_element());
40 affine_element R;
41
42 std::vector<uint8_t> v(64);
43 uint8_t* ptr = v.data();
44 affine_element::serialize_to_buffer(P, ptr);
45
46 R = affine_element::serialize_from_buffer(ptr);
47 ASSERT_TRUE(R.on_curve());
48 ASSERT_TRUE(P == R);
49 }
50
51 // point at infinity
52 {
53 affine_element P = affine_element(element::random_element());
54 P.self_set_infinity();
55 affine_element R;
56
57 std::vector<uint8_t> v(64);
58 uint8_t* ptr = v.data();
59 affine_element::serialize_to_buffer(P, ptr);
60
61 R = affine_element::serialize_from_buffer(ptr);
62 ASSERT_TRUE(R.is_point_at_infinity());
63 ASSERT_TRUE(P == R);
64 }
65 }
66
67 // Verify that serialize_from_buffer rejects off-curve bytes by throwing.
68 static void test_deserialize_off_curve_throws()
69 {
70 using Fq = typename G1::Fq;
71 // Take a valid on-curve point and corrupt its y-coordinate.
72 // P.y + 1 satisfies (y+1)^2 != y^2 (i.e. off-curve) unless 2y + 1 = 0 (prob ~1/p).
73 affine_element P = affine_element(element::random_element());
74 affine_element off_curve;
75 off_curve.x = P.x;
76 off_curve.y = P.y + Fq::one();
77
78 std::vector<uint8_t> v(sizeof(affine_element));
79 uint8_t* ptr = v.data();
80 affine_element::serialize_to_buffer(off_curve, ptr);
81
82 if (!off_curve.on_curve()) {
83#ifndef __wasm__
84 EXPECT_THROW_OR_ABORT(affine_element::serialize_from_buffer(ptr), "not on the curve");
85#endif
86 }
87 }
88
89 static void test_read_and_write()
90 {
91 // a generic point
92 {
93 affine_element P = affine_element(element::random_element());
94 [[maybe_unused]] affine_element R;
95
96 std::vector<uint8_t> v(sizeof(R));
97 uint8_t* ptr = v.data();
98 write(ptr, P);
99 ASSERT_TRUE(P.on_curve());
100
101 // // Reset to start?
102 // ptr = v.data();
103
104 const uint8_t* read_ptr = v.data();
105 // good read
106 read(read_ptr, R);
107 ASSERT_TRUE(R.on_curve());
108 ASSERT_TRUE(P == R);
109 }
110 }
111
112 static void test_msgpack_serialization()
113 {
114 // a generic point
115 {
116 affine_element P = affine_element(element::random_element());
117
118 // Serialize using msgpack
119 msgpack::sbuffer sbuf;
120 msgpack::pack(sbuf, P);
121
122 // Deserialize using msgpack
123 msgpack::object_handle oh = msgpack::unpack(sbuf.data(), sbuf.size());
124 msgpack::object deserialized = oh.get();
125
126 affine_element R;
127 deserialized.convert(R);
128
129 ASSERT_TRUE(R.on_curve() && !R.is_point_at_infinity());
130 ASSERT_TRUE(P == R);
131 }
132
133 // point at infinity
134 {
135 affine_element P = affine_element(element::random_element());
136 P.self_set_infinity();
137
138 // Serialize using msgpack
139 msgpack::sbuffer sbuf;
140 msgpack::pack(sbuf, P);
141
142 // Deserialize using msgpack
143 msgpack::object_handle oh = msgpack::unpack(sbuf.data(), sbuf.size());
144 msgpack::object deserialized = oh.get();
145
146 affine_element R;
147 deserialized.convert(R);
148
149 ASSERT_TRUE(R.is_point_at_infinity());
150 ASSERT_TRUE(P == R);
151 }
152 }
153
154 static void test_point_compression()
155 {
156 for (size_t i = 0; i < 10; i++) {
157 affine_element P = affine_element(element::random_element());
158 uint256_t compressed = uint256_t(P.x);
159 if (uint256_t(P.y).get_bit(0)) {
160 compressed.data[3] |= group_elements::UINT256_TOP_LIMB_MSB;
161 }
162 affine_element Q = affine_element::from_compressed(compressed);
163 EXPECT_EQ(P, Q);
164 }
165 }
166
167 static void test_point_compression_unsafe()
168 {
169 for (size_t i = 0; i < 100; i++) {
170 affine_element P = affine_element(element::random_element());
171 uint256_t compressed = uint256_t(P.x);
172
173 // Note that we do not check the point Q_points[1] because its highly unlikely to hit a point P on the curve
174 // such that r < P.x < q.
175 std::array<affine_element, 2> Q_points = affine_element::from_compressed_unsafe(compressed);
176 EXPECT_EQ(P, Q_points[0]);
177 }
178 }
179
180 static void test_add_affine()
181 {
182 element lhs = element::random_element();
183 affine_element lhs_affine(lhs);
184
185 element rhs = element::random_element();
186 affine_element rhs_affine(rhs);
187
188 element expected = lhs + rhs;
189 affine_element result = lhs_affine + rhs_affine;
190 EXPECT_EQ(element(result) == expected, true);
191 }
192
193 // Regression test to ensure that the point at infinity is not equal to its coordinate-wise reduction, which may lie
194 // on the curve, depending on the y-coordinate.
195 static void test_infinity_regression()
196 {
197 affine_element P;
198 P.self_set_infinity();
199 affine_element R(0, P.y);
200 ASSERT_FALSE(P == R);
201 }
202 static void test_infinity_ordering_regression()
203 {
204 affine_element P(0, 1);
205 affine_element Q(0, 1);
206
207 P.self_set_infinity();
208 EXPECT_NE(P < Q, Q < P);
209 }
210
211 // Verify that from_compressed with an x that has no y on the curve returns the (0,0) sentinel.
212 static void test_point_compression_invalid_x()
213 {
214 using Fq = typename G1::Fq;
215 size_t invalid_count = 0;
216 for (size_t i = 0; i < 20; ++i) {
217 affine_element result = affine_element::from_compressed(uint256_t(Fq::random_element()));
218 if (!result.on_curve()) {
219 ++invalid_count;
220 // from_compressed returns (0, 0) when x has no valid y
221 EXPECT_EQ(result.x, Fq::zero());
222 EXPECT_EQ(result.y, Fq::zero());
223 }
224 }
225 // With 20 trials ~10 should have no valid y, so we almost certainly exercise this path
226 EXPECT_GT(invalid_count, 0U);
227 }
228
233 static void test_batch_endomorphism_by_minus_one()
234 {
235 constexpr size_t num_points = 2;
236 std::vector<affine_element> affine_points(num_points, affine_element::one());
237
239 element::batch_mul_with_endomorphism(affine_points, -affine_element::Fr::one());
240
241 for (size_t i = 0; i < num_points; i++) {
242 EXPECT_EQ(affine_points[i], -result[i]);
243 }
244 }
245
250 static void test_fixed_point_at_infinity()
251 {
252 using Fq = affine_element::Fq;
253 affine_element P = affine_element::infinity();
254 affine_element Q(Fq::zero(), Fq::zero());
255 Q.x.self_set_msb();
256 affine_element R = affine_element(element::random_element());
257 EXPECT_EQ(P, Q);
258 EXPECT_NE(P, R);
259 }
260
261 static void test_infinity_mul_by_scalar_is_infinity()
262 {
263 auto result = affine_element::infinity() * Fr::random_element();
264 EXPECT_TRUE(result.is_point_at_infinity());
265 }
266
267 static void test_batch_mul_matches_non_batch_mul()
268 {
269 constexpr size_t num_points = 512;
270 std::vector<affine_element> affine_points(num_points - 1, affine_element::infinity());
271 affine_points.push_back(affine_element::infinity());
272 Fr exponent = Fr::random_element();
274 std::transform(affine_points.begin(),
275 affine_points.end(),
276 std::back_inserter(expected),
277 [exponent](const auto& el) { return el * exponent; });
278 std::vector<affine_element> result = element::batch_mul_with_endomorphism(affine_points, exponent);
279 EXPECT_THAT(result, ElementsAreArray(expected));
280 }
281
282 static void test_infinity_batch_mul_by_scalar_is_infinity()
283 {
284 constexpr size_t num_points = 1024;
285 std::vector<affine_element> affine_points(num_points, affine_element::infinity());
286 std::vector<affine_element> result = element::batch_mul_with_endomorphism(affine_points, Fr::random_element());
287 EXPECT_THAT(result, Each(Property(&affine_element::is_point_at_infinity, Eq(true))));
288 }
289
290 static void test_batch_mul_endomorphism_even_scalars()
291 {
292 const affine_element P = affine_element::one();
293 const std::vector<affine_element> points(4, P);
294 for (const Fr scalar : { Fr(0), Fr(2), Fr(4), Fr(6) }) {
295 const auto result = element::batch_mul_with_endomorphism(points, scalar);
296 const affine_element expected(element(P) * scalar);
297 for (size_t i = 0; i < points.size(); ++i) {
298 EXPECT_EQ(result[i], expected);
299 }
300 }
301 }
302
303 static void test_frc_codec_round_trip()
304 {
305 using FrField = FrCodec::DataType;
306 affine_element point = affine_element::random_element();
309 affine_element::PUBLIC_INPUTS_SIZE);
310 auto reconstructed = FrCodec::deserialize_from_fields<affine_element>(limbs);
311 EXPECT_EQ(reconstructed, point);
312 }
313
314 // The point at infinity, the generator, and any scalar multiple of the generator must all be
315 // recognized as members of the prime-order subgroup.
316 static void test_is_in_prime_subgroup_accepts_subgroup_points()
317 {
318 EXPECT_TRUE(affine_element::infinity().is_in_prime_subgroup());
319 EXPECT_TRUE(affine_element::one().is_in_prime_subgroup());
320
321 for (size_t i = 0; i < 8; ++i) {
322 affine_element P = affine_element(element::random_element());
323 EXPECT_TRUE(P.is_in_prime_subgroup());
324 }
325 }
326};
327
328// using TestTypes = testing::Types<bb::g1>;
329using TestTypes = testing::Types<bb::g1, grumpkin::g1, secp256k1::g1, secp256r1::g1>;
330} // namespace
331
332TYPED_TEST_SUITE(TestAffineElement, TestTypes);
333
334TYPED_TEST(TestAffineElement, AddAffine)
335{
336 TestFixture::test_add_affine();
337}
338
339TYPED_TEST(TestAffineElement, ReadWrite)
340{
341 TestFixture::test_read_and_write();
342}
343
344TYPED_TEST(TestAffineElement, ReadWriteBuffer)
345{
346 TestFixture::test_read_write_buffer();
347 TestFixture::test_msgpack_serialization();
348}
349
350TYPED_TEST(TestAffineElement, PointCompression)
351{
352 if constexpr (TypeParam::Fq::modulus.data[3] >= MODULUS_TOP_LIMB_LARGE_THRESHOLD) {
353 GTEST_SKIP();
354 } else {
355 TestFixture::test_point_compression();
356 }
357}
358
359TYPED_TEST(TestAffineElement, FixedInfinityPoint)
360{
361 if constexpr (TypeParam::Fq::modulus.data[3] >= MODULUS_TOP_LIMB_LARGE_THRESHOLD) {
362 GTEST_SKIP();
363 } else {
364 TestFixture::test_fixed_point_at_infinity();
365 }
366}
367
368TYPED_TEST(TestAffineElement, PointCompressionUnsafe)
369{
370 if constexpr (TypeParam::Fq::modulus.data[3] >= MODULUS_TOP_LIMB_LARGE_THRESHOLD) {
371 TestFixture::test_point_compression_unsafe();
372 } else {
373 GTEST_SKIP();
374 }
375}
376
377TYPED_TEST(TestAffineElement, InfinityOrderingRegression)
378{
379 TestFixture::test_infinity_ordering_regression();
380}
381
382namespace bb::group_elements {
383// mul_with_endomorphism and mul_without_endomorphism are private in affine_element.
384// We could make those public to test or create other public utilities, but to keep the API intact we
385// instead mark TestElementPrivate as a friend class so that our test functions can have access.
387 public:
388 template <typename Element, typename Scalar>
389 static Element mul_without_endomorphism(const Element& element, const Scalar& scalar)
390 {
391 return element.mul_without_endomorphism(scalar);
392 }
393 template <typename Element, typename Scalar>
394 static Element mul_with_endomorphism(const Element& element, const Scalar& scalar)
395 {
396 return element.mul_with_endomorphism(scalar);
397 }
398};
399} // namespace bb::group_elements
400
401// Our endomorphism-specialized multiplication should match our generic multiplication.
402// Previously only tested on Grumpkin; now runs on every curve that has USE_ENDOMORPHISM.
403TYPED_TEST(TestAffineElement, MulWithEndomorphismMatchesMulWithoutEndomorphism)
404{
405 if constexpr (!TypeParam::USE_ENDOMORPHISM) {
406 GTEST_SKIP();
407 } else {
408 using element_t = typename TypeParam::element;
409 using Fr = typename TypeParam::Fr;
410 for (int i = 0; i < 100; i++) {
411 element_t x1(element_t::random_element());
412 Fr f1 = Fr::random_element();
415 EXPECT_EQ(r1, r2);
416 }
417 }
418}
419
420// FrCodec is defined only for BN254 and Grumpkin (the two curves whose points appear in transcripts).
421TYPED_TEST(TestAffineElement, FrCodecRoundTrip)
422{
424 TestFixture::test_frc_codec_round_trip();
425 } else {
426 GTEST_SKIP();
427 }
428}
429
430// Verify that batch_mul_with_endomorphism gives correct results for even scalars (where k1 or k2 in the
431// GLV decomposition is even), exercising the skew-correction path that uses affine_element::operator+.
432// Scalar 0 gives k1 = k2 = 0 (both skews), and even scalars like 2 and 4 trigger the k1-skew path.
433// These are regression tests for the operator+ fix: reverting to add_chunked would abort when the
434// accumulated result happens to equal ±P during the skew correction.
435TYPED_TEST(TestAffineElement, BatchMulEndomorphismEvenScalars)
436{
437 if constexpr (!TypeParam::USE_ENDOMORPHISM) {
438 GTEST_SKIP();
439 } else {
440 TestFixture::test_batch_mul_endomorphism_even_scalars();
441 }
442}
443
444// Multiplication of a point at infinity by a scalar should be a point at infinity
445TYPED_TEST(TestAffineElement, InfinityMulByScalarIsInfinity)
446{
447 TestFixture::test_infinity_mul_by_scalar_is_infinity();
448}
449
450// Batched multiplication of points should match non-batched multiplication
451TYPED_TEST(TestAffineElement, BatchMulMatchesNonBatchMul)
452{
453 if constexpr (!TypeParam::USE_ENDOMORPHISM) {
454 GTEST_SKIP();
455 } else {
456 TestFixture::test_batch_mul_matches_non_batch_mul();
457 }
458}
459
460// Batched multiplication of a point at infinity by a scalar should result in points at infinity
461TYPED_TEST(TestAffineElement, InfinityBatchMulByScalarIsInfinity)
462{
463 if constexpr (!TypeParam::USE_ENDOMORPHISM) {
464 GTEST_SKIP();
465 } else {
466 TestFixture::test_infinity_batch_mul_by_scalar_is_infinity();
467 }
468}
469
470TYPED_TEST(TestAffineElement, BatchEndomoprhismByMinusOne)
471{
472 if constexpr (TypeParam::USE_ENDOMORPHISM) {
473 TestFixture::test_batch_endomorphism_by_minus_one();
474 } else {
475 GTEST_SKIP();
476 }
477}
478
479// Verify that serialize_from_buffer rejects off-curve bytes by throwing (tests the invalid-curve attack fix).
480TYPED_TEST(TestAffineElement, DeserializeOffCurveThrows)
481{
482 TestFixture::test_deserialize_off_curve_throws();
483}
484
485// Verify is_in_prime_subgroup accepts known prime-order subgroup points
486TYPED_TEST(TestAffineElement, IsInPrimeSubgroupAcceptsSubgroupPoints)
487{
488 TestFixture::test_is_in_prime_subgroup_accepts_subgroup_points();
489}
490
491// Verify that from_compressed returns the (0,0) sentinel for x values with no valid y.
492TYPED_TEST(TestAffineElement, PointCompressionInvalidX)
493{
494 if constexpr (TypeParam::Fq::modulus.data[3] >= MODULUS_TOP_LIMB_LARGE_THRESHOLD) {
495 GTEST_SKIP(); // from_compressed is not used on large-modulus curves
496 } else {
497 TestFixture::test_point_compression_invalid_x();
498 }
499}
500
501TEST(AffineElement, HashToCurve)
502{
504 test_vectors.emplace_back(std::vector<uint8_t>(),
506 fr(uint256_t("24c4cb9c1206ab5470592f237f1698abe684dadf0ab4d7a132c32b2134e2c12e")),
507 fr(uint256_t("0668b8d61a317fb34ccad55c930b3554f1828a0e5530479ecab4defe6bbc0b2e"))));
508
509 test_vectors.emplace_back(std::vector<uint8_t>{ 1 },
511 fr(uint256_t("107f1b633c6113f3222f39f6256f0546b41a4880918c86864b06471afb410454")),
512 fr(uint256_t("050cd3823d0c01590b6a50adcc85d2ee4098668fd28805578aa05a423ea938c6"))));
513
514 // "hello world"
515 test_vectors.emplace_back(std::vector<uint8_t>{ 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64 },
517 fr(uint256_t("037c5c229ae495f6e8d1b4bf7723fafb2b198b51e27602feb8a4d1053d685093")),
518 fr(uint256_t("10cf9596c5b2515692d930efa2cf3817607e4796856a79f6af40c949b066969f"))));
519
520 for (std::tuple<std::vector<uint8_t>, grumpkin::g1::affine_element> test_case : test_vectors) {
521 auto result = grumpkin::g1::affine_element::hash_to_curve(std::get<0>(test_case), 0);
522 auto expected_result = std::get<1>(test_case);
523 std::cout << result << std::endl;
524 EXPECT_TRUE(result == expected_result);
525 }
526}
#define EXPECT_THROW_OR_ABORT(statement, matcher)
Definition assert.hpp:192
static std::vector< fr > serialize_to_fields(const T &val)
Conversion from transcript values to bb::frs.
static Element mul_without_endomorphism(const Element &element, const Scalar &scalar)
static Element mul_with_endomorphism(const Element &element, const Scalar &scalar)
element class. Implements ecc group arithmetic using Jacobian coordinates See https://hyperelliptic....
Definition element.hpp:35
element mul_with_endomorphism(const Fr &scalar) const noexcept
element mul_without_endomorphism(const Fr &scalar) const noexcept
group_elements::affine_element< Fq, Fr, Params > affine_element
Definition group.hpp:44
constexpr bool get_bit(uint64_t bit_index) const
bool expected_result
test_vector test_vectors[]
bb::curve::BN254::Element Element
const size_t num_points
AffineElement * rhs
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...
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
void read(B &it, field2< base_field, Params > &value)
TYPED_TEST_SUITE(CommitmentKeyTest, Curves)
field< Bn254FrParams > fr
Definition fr.hpp:155
void write(B &buf, field2< base_field, Params > const &value)
TYPED_TEST(CommitmentKeyTest, CommitToZeroPoly)
TEST(BoomerangMegaCircuitBuilder, BasicCircuit)
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
testing::Types< VKTestParams< UltraFlavor, stdlib::recursion::honk::DefaultIO< UltraCircuitBuilder > >, VKTestParams< UltraFlavor, stdlib::recursion::honk::RollupIO >, VKTestParams< UltraKeccakFlavor, stdlib::recursion::honk::DefaultIO< UltraCircuitBuilder > >, VKTestParams< MegaFlavor, stdlib::recursion::honk::DefaultIO< MegaCircuitBuilder > > > TestTypes
static constexpr field one()
static field random_element(numeric::RNG *engine=nullptr) noexcept
static constexpr field zero()