Skip to content

Commit cfe9af9

Browse files
committed
finding 7: pin SHA-256 of g2 SRS bytes and verify on download
1 parent d82152a commit cfe9af9

3 files changed

Lines changed: 122 additions & 15 deletions

File tree

barretenberg/cpp/src/barretenberg/srs/factories/bn254_crs_data.hpp

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
#include "barretenberg/ecc/curves/bn254/g1.hpp"
44
#include "barretenberg/ecc/curves/bn254/g2.hpp"
55
#include "barretenberg/numeric/uint256/uint256.hpp"
6+
#include <array>
7+
#include <cstdint>
68

79
namespace bb::srs {
810

@@ -30,25 +32,42 @@ inline g1::affine_element get_bn254_g1_second_element()
3032
return from_buffer<g1::affine_element>(g1_second_data);
3133
}
3234

35+
/**
36+
* @brief Raw 128-byte serialization of the BN254 G2 trusted-setup point [x]_2.
37+
* @details Identical to the contents of `bn254_g2.dat` distributed at
38+
* https://crs.aztec-cdn.foundation/g2.dat. Exposed as a public constant so callers can
39+
* SHA-256-pin the exact CDN bytes (see `BN254_G2_ELEMENT_SHA256` below).
40+
*/
41+
inline constexpr std::array<uint8_t, 128> BN254_G2_ELEMENT_BYTES = {
42+
0x01, 0x18, 0xc4, 0xd5, 0xb8, 0x37, 0xbc, 0xc2, 0xbc, 0x89, 0xb5, 0xb3, 0x98, 0xb5, 0x97, 0x4e, 0x9f, 0x59, 0x44,
43+
0x07, 0x3b, 0x32, 0x07, 0x8b, 0x7e, 0x23, 0x1f, 0xec, 0x93, 0x88, 0x83, 0xb0, 0x26, 0x0e, 0x01, 0xb2, 0x51, 0xf6,
44+
0xf1, 0xc7, 0xe7, 0xff, 0x4e, 0x58, 0x07, 0x91, 0xde, 0xe8, 0xea, 0x51, 0xd8, 0x7a, 0x35, 0x8e, 0x03, 0x8b, 0x4e,
45+
0xfe, 0x30, 0xfa, 0xc0, 0x93, 0x83, 0xc1, 0x22, 0xfe, 0xbd, 0xa3, 0xc0, 0xc0, 0x63, 0x2a, 0x56, 0x47, 0x5b, 0x42,
46+
0x14, 0xe5, 0x61, 0x5e, 0x11, 0xe6, 0xdd, 0x3f, 0x96, 0xe6, 0xce, 0xa2, 0x85, 0x4a, 0x87, 0xd4, 0xda, 0xcc, 0x5e,
47+
0x55, 0x04, 0xfc, 0x63, 0x69, 0xf7, 0x11, 0x0f, 0xe3, 0xd2, 0x51, 0x56, 0xc1, 0xbb, 0x9a, 0x72, 0x85, 0x9c, 0xf2,
48+
0xa0, 0x46, 0x41, 0xf9, 0x9b, 0xa4, 0xee, 0x41, 0x3c, 0x80, 0xda, 0x6a, 0x5f, 0xe4
49+
};
50+
51+
/**
52+
* @brief SHA-256 hash of `BN254_G2_ELEMENT_BYTES`.
53+
* @details Pinned so any G2 ingress (network download, on-disk cache, bbapi caller) can verify it
54+
* is delivering the canonical Aztec trusted-setup [x]_2. Mirrors the `BN254_G1_CHUNK_HASHES`
55+
* mechanism used for the (much larger) G1 CRS. Update this constant only in lockstep with
56+
* `BN254_G2_ELEMENT_BYTES`; the test `CrsFactory.Bn254G2HashMatchesPinnedBytes` enforces this.
57+
*/
58+
inline constexpr std::array<uint8_t, 32> BN254_G2_ELEMENT_SHA256 = { 0x01, 0x79, 0x7b, 0xfc, 0x4d, 0xe5, 0xa9, 0x6f,
59+
0x0e, 0x51, 0x6a, 0x9e, 0xa4, 0x53, 0x7d, 0x18,
60+
0x78, 0x6d, 0xc3, 0x0c, 0xb9, 0x91, 0xac, 0xa4,
61+
0x27, 0x4c, 0x95, 0x82, 0x2b, 0x69, 0xc3, 0x2f };
62+
3363
/**
3464
* @brief Reference BN254 G2 element from the trusted setup CRS
3565
* @details This is the single G2 point used in the BN254 CRS for verification.
3666
* Reference: https://crs.aztec-cdn.foundation/g2.dat
3767
*/
3868
inline g2::affine_element get_bn254_g2_crs_element()
3969
{
40-
// Hardcoded G2 element (128 bytes) - see reference URL above
41-
static constexpr uint8_t g2_data[128] = {
42-
0x01, 0x18, 0xc4, 0xd5, 0xb8, 0x37, 0xbc, 0xc2, 0xbc, 0x89, 0xb5, 0xb3, 0x98, 0xb5, 0x97, 0x4e,
43-
0x9f, 0x59, 0x44, 0x07, 0x3b, 0x32, 0x07, 0x8b, 0x7e, 0x23, 0x1f, 0xec, 0x93, 0x88, 0x83, 0xb0,
44-
0x26, 0x0e, 0x01, 0xb2, 0x51, 0xf6, 0xf1, 0xc7, 0xe7, 0xff, 0x4e, 0x58, 0x07, 0x91, 0xde, 0xe8,
45-
0xea, 0x51, 0xd8, 0x7a, 0x35, 0x8e, 0x03, 0x8b, 0x4e, 0xfe, 0x30, 0xfa, 0xc0, 0x93, 0x83, 0xc1,
46-
0x22, 0xfe, 0xbd, 0xa3, 0xc0, 0xc0, 0x63, 0x2a, 0x56, 0x47, 0x5b, 0x42, 0x14, 0xe5, 0x61, 0x5e,
47-
0x11, 0xe6, 0xdd, 0x3f, 0x96, 0xe6, 0xce, 0xa2, 0x85, 0x4a, 0x87, 0xd4, 0xda, 0xcc, 0x5e, 0x55,
48-
0x04, 0xfc, 0x63, 0x69, 0xf7, 0x11, 0x0f, 0xe3, 0xd2, 0x51, 0x56, 0xc1, 0xbb, 0x9a, 0x72, 0x85,
49-
0x9c, 0xf2, 0xa0, 0x46, 0x41, 0xf9, 0x9b, 0xa4, 0xee, 0x41, 0x3c, 0x80, 0xda, 0x6a, 0x5f, 0xe4
50-
};
51-
return from_buffer<g2::affine_element>(g2_data);
70+
return from_buffer<g2::affine_element>(BN254_G2_ELEMENT_BYTES.data());
5271
}
5372

5473
/**

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

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,8 @@ void check_bn254_consistency(const fs::path& crs_download_path, size_t num_point
2929
// Use get_bn254_g1_data to load reference points (handles compressed/uncompressed automatically)
3030
auto g1_points = bb::get_bn254_g1_data(bb::srs::bb_crs_path(), num_points, /*allow_download=*/false);
3131

32-
// read G2
33-
auto g2_buf = read_file(bb::srs::bb_crs_path() / "bn254_g2.dat", sizeof(g2::affine_element));
34-
auto g2_point = from_buffer<g2::affine_element>(g2_buf);
32+
// read and verify G2 (SHA-256-pinned + subgroup-checked)
33+
auto g2_point = bb::get_bn254_g2_data(bb::srs::bb_crs_path(), /*allow_download=*/false);
3534

3635
// build in-memory CRS
3736
MemBn254CrsFactory mem_crs(g1_points, g2_point);
@@ -133,6 +132,41 @@ TEST(CrsFactory, Bn254HardcodedG2IsInPrimeSubgroup)
133132
EXPECT_TRUE(g2_point.is_in_prime_subgroup());
134133
}
135134

135+
// Locks `BN254_G2_ELEMENT_SHA256` to the actual hash of `BN254_G2_ELEMENT_BYTES`. If anyone edits
136+
// the bytes without recomputing the hash (or vice versa), this fails and forces them to fix it.
137+
TEST(CrsFactory, Bn254G2HashMatchesPinnedBytes)
138+
{
139+
auto hash = bb::crypto::sha256(
140+
std::span<const uint8_t>(bb::srs::BN254_G2_ELEMENT_BYTES.data(), bb::srs::BN254_G2_ELEMENT_BYTES.size()));
141+
EXPECT_EQ(hash, bb::srs::BN254_G2_ELEMENT_SHA256);
142+
}
143+
144+
// Round-trip: the on-disk `bn254_g2.dat` provisioned by `barretenberg/crs/bootstrap.sh` must
145+
// match the pinned canonical bytes byte-for-byte and pass subgroup validation. This catches
146+
// corruption, accidental SRS swaps, or an outdated CDN payload.
147+
TEST(CrsFactory, Bn254G2DataLoadsAndVerifies)
148+
{
149+
auto g2_point = bb::get_bn254_g2_data(bb::srs::bb_crs_path(), /*allow_download=*/false);
150+
EXPECT_EQ(g2_point, bb::srs::get_bn254_g2_crs_element());
151+
}
152+
153+
// A tampered `bn254_g2.dat` (corrupted single byte) must be rejected by the SHA-256 check.
154+
TEST(CrsFactory, Bn254G2CorruptionDetected)
155+
{
156+
const std::filesystem::path temp_path = "barretenberg_srs_test_crs_g2_corruption";
157+
fs::remove_all(temp_path);
158+
fs::create_directories(temp_path);
159+
160+
auto corrupted =
161+
std::vector<uint8_t>(bb::srs::BN254_G2_ELEMENT_BYTES.begin(), bb::srs::BN254_G2_ELEMENT_BYTES.end());
162+
corrupted[64] ^= 0xFF;
163+
bb::write_file(temp_path / "bn254_g2.dat", corrupted);
164+
165+
EXPECT_THROW_OR_ABORT(bb::get_bn254_g2_data(temp_path, /*allow_download=*/false), "SHA-256 mismatch");
166+
167+
fs::remove_all(temp_path);
168+
}
169+
136170
TEST(CrsFactory, Bn254CompressedChunkHashFirstChunk)
137171
{
138172
// 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: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,4 +276,58 @@ std::vector<g1::affine_element> get_bn254_g1_data(const std::filesystem::path& p
276276
return get_bn254_g1_data(path, num_points, allow_download, CRS_PRIMARY_URL, CRS_FALLBACK_URL);
277277
}
278278

279+
// Verifies the canonical 128-byte serialization of [x]_2 against the pinned SHA-256 and the BN254
280+
// G2 prime-order subgroup. This is the equivalent of the chunk-hash verification we do for the G1
281+
// CRS (`verify_bn254_crs_integrity`) and is the only thing that ties an on-disk or downloaded G2
282+
// payload to the trusted setup; without it, a tampered `bn254_g2.dat` would propagate to every
283+
// pairing-based verifier consuming the cached file.
284+
g2::affine_element get_bn254_g2_data(const std::filesystem::path& path, bool allow_download)
285+
{
286+
std::filesystem::create_directories(path);
287+
constexpr size_t G2_BYTES = 128;
288+
auto g2_path = path / "bn254_g2.dat";
289+
auto lock_path = path / "crs.lock";
290+
FileLockGuard lock(lock_path.string());
291+
292+
auto verify_and_parse = [](const std::vector<uint8_t>& data) {
293+
if (data.size() != G2_BYTES) {
294+
throw_or_abort("bn254 g2 data has wrong size: expected 128 bytes, got " + std::to_string(data.size()));
295+
}
296+
auto hash = bb::crypto::sha256(std::span<const uint8_t>(data.data(), data.size()));
297+
if (hash != bb::srs::BN254_G2_ELEMENT_SHA256) {
298+
throw_or_abort("bn254 g2 SHA-256 mismatch: payload does not match the canonical [x]_2");
299+
}
300+
auto point = from_buffer<g2::affine_element>(data.data());
301+
if (!point.is_in_prime_subgroup()) {
302+
throw_or_abort("bn254 g2 deserialized to a point outside the prime-order subgroup");
303+
}
304+
return point;
305+
};
306+
307+
if (get_file_size(g2_path) == G2_BYTES) {
308+
return verify_and_parse(read_file(g2_path, G2_BYTES));
309+
}
310+
if (!allow_download) {
311+
throw_or_abort("bn254 g2 data not found at " + path.string() +
312+
" and bb does not automatically download in this context."
313+
" Run barretenberg/crs/bootstrap.sh to download.");
314+
}
315+
316+
vinfo("downloading bn254 g2...");
317+
std::vector<uint8_t> data;
318+
#ifndef __wasm__
319+
try {
320+
data = bb::srs::http_download(std::string("http://crs.aztec-cdn.foundation/g2.dat"), 0, G2_BYTES - 1);
321+
} catch (const std::exception& e) {
322+
vinfo("Primary g2 download failed: ", e.what(), ". Trying fallback...");
323+
data = bb::srs::http_download(std::string("http://crs.aztec-labs.com/g2.dat"), 0, G2_BYTES - 1);
324+
}
325+
#else
326+
data = bb::srs::http_download(std::string("http://crs.aztec-cdn.foundation/g2.dat"), 0, G2_BYTES - 1);
327+
#endif
328+
auto point = verify_and_parse(data);
329+
write_file(g2_path, data);
330+
return point;
331+
}
332+
279333
} // namespace bb

0 commit comments

Comments
 (0)