Skip to content

Commit 2c23818

Browse files
committed
finding 7: hash-pin g1 and g2 SRS at bbapi entry
1 parent cfe9af9 commit 2c23818

1 file changed

Lines changed: 41 additions & 5 deletions

File tree

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

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,15 @@
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

1418
namespace 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

Comments
 (0)