Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,37 @@ TEST(BN254Fq, SplitIntoEndomorphismScalarsSimple)
}
}

// Regression: k = ceil(m * 2^256 / endo_g2), for m an integer, previously produced negative k2 in the GLV
// splitting, causing 128-bit truncation to extract wrong values. See endomorphism_scalars.py.
TEST(BN254Fq, SplitEndomorphismNegativeK2)
{
// clang-format off
struct test_case { std::array<uint64_t, 4> limbs; const char* tag; };
const std::array<test_case, 3> cases = {{
{{ 0x71922da036dca5f4, 0xd970a56127fb8227, 0x59e26bcea0d48bac, 0x0 }, "m=1"},
{{ 0xe3245b406db94be8, 0xb2e14ac24ff7044e, 0xb3c4d79d41a91759, 0x0 }, "m=2"},
{{ 0x54b688e0a495f1dc, 0x8c51f02377f28676, 0x0da7436be27da306, 0x1 }, "m=3"},
}};
// clang-format on

fq lambda = fq::cube_root_of_unity();

for (const auto& tc : cases) {
fq k{ tc.limbs[0], tc.limbs[1], tc.limbs[2], tc.limbs[3] };
fq k1{ 0, 0, 0, 0 };
fq k2{ 0, 0, 0, 0 };

fq::split_into_endomorphism_scalars(k, k1, k2);

k1.self_to_montgomery_form();
k2.self_to_montgomery_form();
fq result = k1 - k2 * lambda;
result.self_from_montgomery_form();

EXPECT_EQ(result, k) << tc.tag;
}
}

TEST(BN254Fq, SplitIntoEndomorphismEdgeCase)
{
fq input = { 0, 0, 1, 0 }; // 2^128
Expand Down
32 changes: 31 additions & 1 deletion barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fr.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
* Other field arithmetic tests (both compile-time and runtime) are in ecc/fields/generic_field.test.cpp and
* ecc/fields/prime_field.test.cpp. This file contains only BN254 scalar field specific functionality:
* - Fixed compile-time tests with field-specific expected values
* - Multiplicative generator (AUDITTODO: delete)
* - Endomorphism scalar decomposition
*/

Expand Down Expand Up @@ -122,6 +121,37 @@ TEST(BN254Fr, SplitIntoEndomorphismScalarsSimple)
}
}

// Regression: k = ceil(m * 2^256 / endo_g2), for m an integer, previously produced negative k2 in the GLV
// splitting, causing 128-bit truncation to extract wrong values.
TEST(BN254Fr, SplitEndomorphismNegativeK2)
{
// clang-format off
struct test_case { std::array<uint64_t, 4> limbs; const char* tag; };
const std::array<test_case, 3> cases = {{
{{ 0x01624731e1195570, 0x3ba491482db4da14, 0x59e26bcea0d48bac, 0x0 }, "m=1"},
{{ 0x02c48e63c232aadf, 0x774922905b69b428, 0xb3c4d79d41a91758, 0x0 }, "m=2"},
{{ 0x0426d595a34c004e, 0xb2edb3d8891e8e3c, 0x0da7436be27da304, 0x1 }, "m=3"},
}};
// clang-format on

fr lambda = fr::cube_root_of_unity();

for (const auto& tc : cases) {
fr k{ tc.limbs[0], tc.limbs[1], tc.limbs[2], tc.limbs[3] };
fr k1{ 0, 0, 0, 0 };
fr k2{ 0, 0, 0, 0 };

fr::split_into_endomorphism_scalars(k, k1, k2);

k1.self_to_montgomery_form();
k2.self_to_montgomery_form();
fr result = k1 - k2 * lambda;
result.self_from_montgomery_form();

EXPECT_EQ(result, k) << tc.tag;
}
}

// ================================
// Regression / Optimization Tests
// ================================
Expand Down
57 changes: 57 additions & 0 deletions barretenberg/cpp/src/barretenberg/ecc/curves/bn254/g1.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,24 @@

using namespace bb;

namespace {
// Double-and-add scalar mul without endomorphism, used as reference for differential testing.
template <typename Group, typename Fr>
typename Group::affine_element naive_scalar_mul(const typename Group::element& base, const Fr& scalar)
{
typename Group::element acc = Group::point_at_infinity;
typename Group::element runner = base;
uint256_t bits(scalar);
for (size_t i = 0; i < 256; ++i) {
if (bits.get_bit(i)) {
acc = acc + runner;
}
runner = runner.dbl();
}
return typename Group::affine_element(acc);
}
} // namespace

TEST(g1, RandomElement)
{
g1::element result = g1::element::random_element();
Expand Down Expand Up @@ -428,3 +446,42 @@ TEST(g1, CheckPrecomputedGenerators)
ASSERT_TRUE((bb::check_precomputed_generators<g1, "ECCVM_OFFSET_GENERATOR", 1UL>()));
ASSERT_TRUE((bb::check_precomputed_generators<g1, "test generators", 2UL>()));
}

// Regression: boundary scalars k = ceil(m * 2^256 / endo_g2) (from endomorphism_scalars.py)
// previously triggered the negative-k2 bug in split_into_endomorphism_scalars, producing wrong
// scalar multiplication results. We test boundaries and random samples within each band.
TEST(g1, ScalarMulNegativeK2Regression)
{
// clang-format off
struct test_case { std::array<uint64_t, 4> limbs; const char* tag; };
const std::array<test_case, 3> boundary_cases = {{
{{ 0x01624731e1195570, 0x3ba491482db4da14, 0x59e26bcea0d48bac, 0x0 }, "m=1"},
{{ 0x02c48e63c232aadf, 0x774922905b69b428, 0xb3c4d79d41a91758, 0x0 }, "m=2"},
{{ 0x0426d595a34c004e, 0xb2edb3d8891e8e3c, 0x0da7436be27da304, 0x1 }, "m=3"},
}};
// clang-format on

for (const auto& tc : boundary_cases) {
fr base_scalar{ tc.limbs[0], tc.limbs[1], tc.limbs[2], tc.limbs[3] };
base_scalar.self_to_montgomery_form();

g1::affine_element endo_result(g1::one * base_scalar);
g1::affine_element naive_result = naive_scalar_mul<g1, fr>(g1::one, base_scalar);
EXPECT_EQ(naive_result.on_curve(), true) << tc.tag;
EXPECT_EQ(endo_result.on_curve(), true) << tc.tag;
EXPECT_EQ(endo_result, naive_result) << tc.tag;

// Random samples within the formerly-buggy band (~2^123-2^126 wide; 122-bit offsets).
for (size_t i = 0; i < 100; ++i) {
uint256_t rand_bits(fr::random_element());
uint256_t offset_int = (rand_bits & ((uint256_t(1) << 122) - 1)) + 1;
fr scalar = base_scalar + fr(offset_int);

g1::affine_element endo_res(g1::one * scalar);
g1::affine_element naive_res = naive_scalar_mul<g1, fr>(g1::one, scalar);
EXPECT_EQ(naive_res.on_curve(), true) << tc.tag << " offset " << i;
EXPECT_EQ(endo_res.on_curve(), true) << tc.tag << " offset " << i;
EXPECT_EQ(endo_res, naive_res) << tc.tag << " offset " << i;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,24 @@

using namespace bb;

namespace {
// Double-and-add scalar mul without endomorphism, used as reference for differential testing.
template <typename Group, typename Fr>
typename Group::affine_element naive_scalar_mul(const typename Group::element& base, const Fr& scalar)
{
typename Group::element acc = Group::point_at_infinity;
typename Group::element runner = base;
uint256_t bits(scalar);
for (size_t i = 0; i < 256; ++i) {
if (bits.get_bit(i)) {
acc = acc + runner;
}
runner = runner.dbl();
}
return typename Group::affine_element(acc);
}
} // namespace

TEST(grumpkin, CheckB)
{
auto b = grumpkin::g1::curve_b;
Expand Down Expand Up @@ -336,3 +354,44 @@ TEST(grumpkin, CheckPrecomputedGenerators)
ASSERT_TRUE((bb::check_precomputed_generators<grumpkin::g1, "pedersen_hash_length", 1UL>()));
ASSERT_TRUE((bb::check_precomputed_generators<grumpkin::g1, "DEFAULT_DOMAIN_SEPARATOR", 8UL>()));
}

// Regression: boundary scalars k = ceil(m * 2^256 / endo_g2) (from endomorphism_scalars.py)
// previously triggered the negative-k2 bug in split_into_endomorphism_scalars, producing wrong
// scalar multiplication results. We test boundaries and random samples within each band.
TEST(grumpkin, ScalarMulNegativeK2Regression)
{
// clang-format off
struct test_case { std::array<uint64_t, 4> limbs; const char* tag; };
const std::array<test_case, 3> boundary_cases = {{
{{ 0x71922da036dca5f4, 0xd970a56127fb8227, 0x59e26bcea0d48bac, 0x0 }, "m=1"},
{{ 0xe3245b406db94be8, 0xb2e14ac24ff7044e, 0xb3c4d79d41a91759, 0x0 }, "m=2"},
{{ 0x54b688e0a495f1dc, 0x8c51f02377f28676, 0x0da7436be27da306, 0x1 }, "m=3"},
}};
// clang-format on

for (const auto& tc : boundary_cases) {
grumpkin::fr base_scalar{ tc.limbs[0], tc.limbs[1], tc.limbs[2], tc.limbs[3] };
base_scalar.self_to_montgomery_form();

grumpkin::g1::affine_element endo_result(grumpkin::g1::one * base_scalar);
grumpkin::g1::affine_element naive_result =
naive_scalar_mul<grumpkin::g1, grumpkin::fr>(grumpkin::g1::one, base_scalar);
EXPECT_EQ(naive_result.on_curve(), true) << tc.tag;
EXPECT_EQ(endo_result.on_curve(), true) << tc.tag;
EXPECT_EQ(endo_result, naive_result) << tc.tag;

// Random samples within the formerly-buggy band (~2^123-2^126 wide; 122-bit offsets).
for (size_t i = 0; i < 100; ++i) {
uint256_t rand_bits(grumpkin::fr::random_element());
uint256_t offset_int = (rand_bits & ((uint256_t(1) << 122) - 1)) + 1;
grumpkin::fr scalar = base_scalar + grumpkin::fr(offset_int);

grumpkin::g1::affine_element endo_res(grumpkin::g1::one * scalar);
grumpkin::g1::affine_element naive_res =
naive_scalar_mul<grumpkin::g1, grumpkin::fr>(grumpkin::g1::one, scalar);
EXPECT_EQ(naive_res.on_curve(), true) << tc.tag << " offset " << i;
EXPECT_EQ(endo_res.on_curve(), true) << tc.tag << " offset " << i;
EXPECT_EQ(endo_res, naive_res) << tc.tag << " offset " << i;
}
}
}
Loading