55#include " barretenberg/bbapi/bbapi_srs.hpp"
66#include " barretenberg/common/serialize.hpp"
77#include " barretenberg/common/thread.hpp"
8+ #include " barretenberg/crypto/sha256/sha256.hpp"
89#include " barretenberg/ecc/curves/bn254/g1.hpp"
910#include " barretenberg/ecc/curves/bn254/g2.hpp"
1011#include " barretenberg/ecc/curves/grumpkin/grumpkin.hpp"
1112#include " barretenberg/numeric/uint256/uint256.hpp"
13+ #include " barretenberg/srs/factories/bn254_crs_data.hpp"
14+ #include " barretenberg/srs/factories/bn254_g1_chunk_hashes.hpp"
1215#include " barretenberg/srs/global_crs.hpp"
16+ #include < span>
1317
1418namespace bb ::bbapi {
1519
@@ -30,6 +34,24 @@ SrsInitSrs::Response SrsInitSrs::execute(BB_UNUSED BBApiRequest& request) &&
3034 }
3135 });
3236 } else if (bytes_per_point == COMPRESSED_POINT_SIZE ) {
37+ // Verify SHA-256 of every fully-present 4 MB chunk against the in-binary pin
38+ // BN254_G1_CHUNK_HASHES before decompression. This is the same defense as
39+ // verify_bn254_crs_integrity used by get_bn254_g1_data on the C++ download path; without
40+ // it, bb.js (which downloads g1_compressed.dat externally and forwards the bytes here)
41+ // would have no cryptographic gate against a tampered or wrong-trusted-setup payload.
42+ // Partial trailing data is not chunk-hash-verified — instead the post-parse generator and
43+ // tau·G checks below close the small-num_points gap.
44+ size_t num_full_chunks = points_buf.size () / bb::srs::SRS_CHUNK_SIZE_BYTES ;
45+ size_t chunks_to_verify = std::min (num_full_chunks, static_cast <size_t >(bb::srs::SRS_NUM_FULL_CHUNKS ));
46+ for (size_t i = 0 ; i < chunks_to_verify; ++i) {
47+ auto chunk = std::span<const uint8_t >(points_buf.data () + i * bb::srs::SRS_CHUNK_SIZE_BYTES ,
48+ bb::srs::SRS_CHUNK_SIZE_BYTES );
49+ auto hash = bb::crypto::sha256 (chunk);
50+ if (hash != bb::srs::BN254_G1_CHUNK_HASHES [i]) {
51+ throw_or_abort (" SrsInitSrs: g1 compressed chunk " + std::to_string (i) + " SHA-256 mismatch" );
52+ }
53+ }
54+
3355 // Compressed: decompress and return uncompressed bytes for caller to cache
3456 parallel_for ([&](ThreadChunk chunk) {
3557 for (auto i : chunk.range (static_cast <size_t >(num_points))) {
@@ -50,11 +72,25 @@ SrsInitSrs::Response SrsInitSrs::execute(BB_UNUSED BBApiRequest& request) &&
5072 std::to_string (bytes_per_point));
5173 }
5274
53- // Parse G2 point from buffer (128 bytes). `serialize_from_buffer` validates that the bytes
54- // decode to a curve point but does NOT enforce subgroup membership. BN254 G2 has a non-trivial
55- // cofactor (h2 ≈ 2^254), so a curve point may lie in a small cofactor subgroup of order
56- // dividing h2 rather than the prime-order subgroup of order r. Reject anything outside
57- // the prime-order subgroup before it reaches the SRS factory.
75+ // Parsed-form sanity check that pins the first two G1 points to their canonical trusted-setup
76+ // values. Catches a wrong-SRS swap even when num_points is below one chunk (where the
77+ // compressed-chunk hash loop above has nothing to verify) and runs identically for the
78+ // uncompressed input path.
79+ if (num_points >= 1 && g1_points[0 ] != bb::srs::BN254_G1_FIRST_ELEMENT ) {
80+ throw_or_abort (" SrsInitSrs: g1_points[0] is not the canonical BN254 generator" );
81+ }
82+ if (num_points >= 2 && g1_points[1 ] != bb::srs::get_bn254_g1_second_element ()) {
83+ throw_or_abort (" SrsInitSrs: g1_points[1] does not match the canonical trusted-setup tau·G" );
84+ }
85+
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.
90+ auto g2_hash = bb::crypto::sha256 (std::span<const uint8_t >(g2_point.data (), g2_point.size ()));
91+ if (g2_hash != bb::srs::BN254_G2_ELEMENT_SHA256 ) {
92+ throw_or_abort (" SrsInitSrs: g2_point bytes do not match the canonical Aztec [x]_2 SHA-256" );
93+ }
5894 auto g2_point_elem = from_buffer<g2::affine_element>(g2_point.data ());
5995 if (!g2_point_elem.is_in_prime_subgroup ()) {
6096 throw_or_abort (" SrsInitSrs: g2_point is not in the BN254 G2 prime-order subgroup" );
0 commit comments