Skip to content

Commit b61727c

Browse files
committed
finding 7: reject g2 = point at infinity
1 parent c602ca8 commit b61727c

3 files changed

Lines changed: 44 additions & 6 deletions

File tree

barretenberg/cpp/src/barretenberg/bbapi/bbapi_srs.cpp

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,15 +83,27 @@ SrsInitSrs::Response SrsInitSrs::execute(BB_UNUSED BBApiRequest& request) &&
8383
throw_or_abort("SrsInitSrs: g1_points[1] does not match the canonical trusted-setup tau·G");
8484
}
8585

86-
// Defense in depth: hash-pin AND subgroup-check the G2 input. Hash equality alone is sufficient
87-
// for the canonical case (it implies prime-order membership); the subgroup check is kept so
88-
// that any future relaxation of the hash gate (e.g. a flag to allow a different trusted setup)
89-
// does not silently reopen audit finding #7's small-subgroup attack.
86+
// Defense in depth: deserialize G2, then run three independent gates (infinity / hash / subgroup).
87+
//
88+
// Why all three:
89+
// - is_point_at_infinity(): `[x]_2 = O` is in G_r (so subgroup check passes) and matches no
90+
// canonical hash, but it would degenerate the KZG verifier — `e(−W, O) = 1` for every W,
91+
// so the second pairing in the opening check is identically 1 and a malicious prover can
92+
// forge openings for arbitrary statements. This must be rejected even if the hash gate is
93+
// ever loosened (e.g. a future "allow custom SRS" flag).
94+
// - SHA-256 vs BN254_G2_ELEMENT_SHA256: pins the exact canonical Aztec trusted-setup [x]_2,
95+
// blocking byte-level tampering and wrong-trusted-setup swaps.
96+
// - is_in_prime_subgroup(): rejects cofactor-subgroup points (audit finding #7's original
97+
// small-subgroup attack); strictly redundant given the hash pin but kept so that loosening
98+
// the hash gate does not silently reopen the cofactor attack either.
99+
auto g2_point_elem = from_buffer<g2::affine_element>(g2_point.data());
100+
if (g2_point_elem.is_point_at_infinity()) {
101+
throw_or_abort("SrsInitSrs: g2_point cannot be the point at infinity");
102+
}
90103
auto g2_hash = bb::crypto::sha256(std::span<const uint8_t>(g2_point.data(), g2_point.size()));
91104
if (g2_hash != bb::srs::BN254_G2_ELEMENT_SHA256) {
92105
throw_or_abort("SrsInitSrs: g2_point bytes do not match the canonical Aztec [x]_2 SHA-256");
93106
}
94-
auto g2_point_elem = from_buffer<g2::affine_element>(g2_point.data());
95107
if (!g2_point_elem.is_in_prime_subgroup()) {
96108
throw_or_abort("SrsInitSrs: g2_point is not in the BN254 G2 prime-order subgroup");
97109
}

barretenberg/cpp/src/barretenberg/srs/factories/crs_factory.test.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,26 @@ TEST(CrsFactory, Bn254G2CorruptionDetected)
167167
fs::remove_all(temp_path);
168168
}
169169

170+
// Regression test: a `bn254_g2.dat` whose 128 bytes are the all-1s infinity sentinel
171+
// deserializes to `affine_element::infinity()`. That point is a member of every subgroup
172+
// (so the subgroup check alone would let it through), but `[x]_2 = O` collapses the KZG
173+
// pairing check (`e(−W, O) = 1` for every W) and lets a malicious prover forge openings.
174+
// The loader must reject it with the explicit infinity error before the SHA-256 / subgroup
175+
// checks fire.
176+
TEST(CrsFactory, Bn254G2InfinityRejected)
177+
{
178+
const std::filesystem::path temp_path = "barretenberg_srs_test_crs_g2_infinity";
179+
fs::remove_all(temp_path);
180+
fs::create_directories(temp_path);
181+
182+
std::vector<uint8_t> infinity_bytes(128, 0xFF);
183+
bb::write_file(temp_path / "bn254_g2.dat", infinity_bytes);
184+
185+
EXPECT_THROW_OR_ABORT(bb::get_bn254_g2_data(temp_path, /*allow_download=*/false), "point at infinity");
186+
187+
fs::remove_all(temp_path);
188+
}
189+
170190
TEST(CrsFactory, Bn254CompressedChunkHashFirstChunk)
171191
{
172192
// Download the first chunk of compressed CRS from CDN and verify its hash.

barretenberg/cpp/src/barretenberg/srs/factories/get_bn254_crs.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,11 +293,17 @@ g2::affine_element get_bn254_g2_data(const std::filesystem::path& path, bool all
293293
if (data.size() != G2_BYTES) {
294294
throw_or_abort("bn254 g2 data has wrong size: expected 128 bytes, got " + std::to_string(data.size()));
295295
}
296+
auto point = from_buffer<g2::affine_element>(data.data());
297+
// Reject the point at infinity: it is a member of every subgroup (so subgroup check passes)
298+
// but `e(−W, O) = 1` for every W, which collapses the KZG verifier's pairing check and lets
299+
// a malicious prover forge arbitrary openings.
300+
if (point.is_point_at_infinity()) {
301+
throw_or_abort("bn254 g2 cannot be the point at infinity");
302+
}
296303
auto hash = bb::crypto::sha256(std::span<const uint8_t>(data.data(), data.size()));
297304
if (hash != bb::srs::BN254_G2_ELEMENT_SHA256) {
298305
throw_or_abort("bn254 g2 SHA-256 mismatch: payload does not match the canonical [x]_2");
299306
}
300-
auto point = from_buffer<g2::affine_element>(data.data());
301307
if (!point.is_in_prime_subgroup()) {
302308
throw_or_abort("bn254 g2 deserialized to a point outside the prime-order subgroup");
303309
}

0 commit comments

Comments
 (0)