Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
get_bn254_crs.cpp
Go to the documentation of this file.
1#include "get_bn254_crs.hpp"
9#include "bn254_crs_data.hpp"
11#include "http_download.hpp"
12#include <atomic>
13#include <span>
14
15namespace {
16// Primary CRS URL (Cloudflare R2)
17constexpr const char* CRS_PRIMARY_URL = "http://crs.aztec-cdn.foundation/g1_compressed.dat";
18// Fallback CRS URL (AWS S3)
19constexpr const char* CRS_FALLBACK_URL = "http://crs.aztec-labs.com/g1_compressed.dat";
20constexpr size_t COMPRESSED_POINT_SIZE = 32;
21constexpr size_t UNCOMPRESSED_POINT_SIZE = 64; // sizeof(g1::affine_element)
22
26void write_uncompressed_g1_points(const std::vector<bb::g1::affine_element>& points, const std::filesystem::path& path)
27{
28 std::vector<uint8_t> buf(points.size() * UNCOMPRESSED_POINT_SIZE);
30 for (auto i : chunk.range(points.size())) {
31 auto serialized = to_buffer(points[i]);
32 std::copy(serialized.begin(), serialized.end(), &buf[i * UNCOMPRESSED_POINT_SIZE]);
33 }
34 });
35 bb::write_file(path, buf);
36}
37
41std::vector<bb::g1::affine_element> read_uncompressed_g1_points(const std::filesystem::path& path, size_t num_points)
42{
43 auto data = bb::read_file(path, num_points * UNCOMPRESSED_POINT_SIZE);
44 std::vector<bb::g1::affine_element> points(num_points);
46 for (auto i : chunk.range(num_points)) {
47 points[i] = from_buffer<bb::g1::affine_element>(data, i * UNCOMPRESSED_POINT_SIZE);
48 }
49 });
50 return points;
51}
52
56size_t round_up_to_chunk_boundary(size_t num_points)
57{
58 if (num_points >= bb::srs::SRS_TOTAL_POINTS) {
59 return bb::srs::SRS_TOTAL_POINTS;
60 }
61 size_t rounded = ((num_points + bb::srs::SRS_CHUNK_SIZE_POINTS - 1) / bb::srs::SRS_CHUNK_SIZE_POINTS) *
62 bb::srs::SRS_CHUNK_SIZE_POINTS;
63 return std::min(rounded, bb::srs::SRS_TOTAL_POINTS);
64}
65
85void verify_bn254_crs_integrity(const std::vector<uint8_t>& data)
86{
87 size_t num_full_chunks = data.size() / bb::srs::SRS_CHUNK_SIZE_BYTES;
88 size_t chunks_to_verify = std::min(num_full_chunks, static_cast<size_t>(bb::srs::SRS_NUM_FULL_CHUNKS));
89
90 // Sentinel value means "no failure found yet"
91 const size_t sentinel = bb::srs::SRS_NUM_CHUNKS;
92 std::atomic<size_t> failed_chunk{ sentinel };
93
94 // Verify all complete chunks in parallel
95 if (chunks_to_verify > 0) {
96 bb::parallel_for([&](const bb::ThreadChunk& tc) {
97 for (size_t i : tc.range(chunks_to_verify)) {
98 // Early exit if another thread already found a mismatch
99 if (failed_chunk.load(std::memory_order_relaxed) < sentinel) {
100 return;
101 }
102 size_t offset = i * bb::srs::SRS_CHUNK_SIZE_BYTES;
103 auto chunk = std::span<const uint8_t>(data.data() + offset, bb::srs::SRS_CHUNK_SIZE_BYTES);
104 auto hash = bb::crypto::sha256(chunk);
105 if (hash != bb::srs::BN254_G1_CHUNK_HASHES[i]) {
106 size_t expected = sentinel;
107 failed_chunk.compare_exchange_strong(expected, i, std::memory_order_relaxed);
108 }
109 }
110 });
111 }
112
113 // Verify partial last chunk (e.g. the 32-byte tail of the full CRS)
114 size_t tail_offset = chunks_to_verify * bb::srs::SRS_CHUNK_SIZE_BYTES;
115 size_t tail_size = data.size() - tail_offset;
116 if (tail_size > 0 && chunks_to_verify < bb::srs::SRS_NUM_CHUNKS) {
117 auto tail = std::span<const uint8_t>(data.data() + tail_offset, tail_size);
118 auto hash = bb::crypto::sha256(tail);
119 if (hash != bb::srs::BN254_G1_CHUNK_HASHES[chunks_to_verify]) {
120 size_t expected = sentinel;
121 failed_chunk.compare_exchange_strong(expected, chunks_to_verify, std::memory_order_relaxed);
122 }
123 }
124
125 size_t bad = failed_chunk.load();
126 if (bad < sentinel) {
127 size_t offset = bad * bb::srs::SRS_CHUNK_SIZE_BYTES;
128 throw_or_abort("CRS integrity check failed: SHA-256 mismatch at chunk " + std::to_string(bad) + " (bytes " +
129 std::to_string(offset) + "+)");
130 }
131
132 vinfo("verified ", chunks_to_verify + (tail_size > 0 ? 1 : 0), " BN254 G1 CRS chunks via SHA-256");
133}
134
138std::vector<bb::g1::affine_element> decompress_g1_points(const std::vector<uint8_t>& data, size_t num_points)
139{
140 std::vector<bb::g1::affine_element> points(num_points);
142 for (auto i : chunk.range(num_points)) {
143 uint256_t compressed = from_buffer<uint256_t>(data, i * COMPRESSED_POINT_SIZE);
144 points[i] = bb::g1::affine_element::from_compressed(compressed);
145 }
146 });
147 return points;
148}
149
150std::vector<uint8_t> download_bn254_g1_data(size_t num_points,
151 const std::string& primary_url,
152 const std::string& fallback_url)
153{
154 // Round up to chunk boundary so every downloaded byte is hash-verified
155 size_t download_points = round_up_to_chunk_boundary(num_points);
156 size_t g1_end = (download_points * COMPRESSED_POINT_SIZE) - 1;
157
158 // Try primary URL first, with fallback on failure.
159 // Note: WASM is compiled with -fno-exceptions, so try/catch is not available.
160 // In practice, WASM never calls this function - it initializes CRS via srs_init_srs from JavaScript.
161 std::vector<uint8_t> data;
162#ifndef __wasm__
163 try {
164 data = bb::srs::http_download(primary_url, 0, g1_end);
165 } catch (const std::exception& e) {
166 vinfo("Primary CRS download failed: ", e.what(), ". Trying fallback...");
167 data = bb::srs::http_download(fallback_url, 0, g1_end);
168 }
169#else
170 // WASM fallback: just try primary (will abort on failure)
171 data = bb::srs::http_download(primary_url, 0, g1_end);
172 static_cast<void>(fallback_url);
173#endif
174
175 if (data.size() < COMPRESSED_POINT_SIZE) {
176 throw_or_abort("Downloaded g1 data is too small");
177 }
178
179 // Quick sanity check: verify the first two G1 points match expected values
180 auto first = from_buffer<uint256_t>(data, 0);
182 throw_or_abort("Downloaded BN254 G1 CRS first element does not match expected point.");
183 }
184
185 if (data.size() >= 2 * COMPRESSED_POINT_SIZE) {
186 auto second = from_buffer<uint256_t>(data, COMPRESSED_POINT_SIZE);
188 throw_or_abort("Downloaded BN254 G1 CRS second element does not match expected point.");
189 }
190 }
191
192 // Full integrity verification: SHA-256 chunk hashes in parallel
193 verify_bn254_crs_integrity(data);
194
195 return data;
196}
197
198} // namespace
199
200namespace bb {
201
202// Main implementation with configurable URLs
203std::vector<g1::affine_element> get_bn254_g1_data(const std::filesystem::path& path,
204 size_t num_points,
205 bool allow_download,
206 const std::string& primary_url,
207 const std::string& fallback_url)
208{
209 BB_BENCH_NAME("get_bn254_g1_data");
210 std::filesystem::create_directories(path);
211
212 auto uncompressed_path = path / "bn254_g1.dat";
213 auto compressed_path = path / "bn254_g1_compressed.dat";
214 auto lock_path = path / "crs.lock";
215 // Acquire exclusive lock to prevent simultaneous downloads
216 FileLockGuard lock(lock_path.string());
217
218 // 1. Prefer cached uncompressed (fastest: parallel from_buffer, ~0.3s for 2^20 points)
219 size_t uncompressed_points = get_file_size(uncompressed_path) / UNCOMPRESSED_POINT_SIZE;
220 if (uncompressed_points >= num_points) {
221 vinfo("using cached uncompressed bn254 crs with ", uncompressed_points, " points at ", uncompressed_path);
222 return read_uncompressed_g1_points(uncompressed_path, num_points);
223 }
224
225 // 2. Fall back to compressed on disk: decompress and cache uncompressed
226 size_t compressed_points = get_file_size(compressed_path) / COMPRESSED_POINT_SIZE;
227 if (compressed_points >= num_points) {
228 vinfo("decompressing cached compressed bn254 crs (", compressed_points, " points)...");
229 auto data = read_file(compressed_path, num_points * COMPRESSED_POINT_SIZE);
230 auto points = decompress_g1_points(data, num_points);
231 write_uncompressed_g1_points(points, uncompressed_path);
232 vinfo("cached uncompressed bn254 crs at ", uncompressed_path);
233 return points;
234 }
235
236 if (!allow_download && compressed_points == 0) {
237 throw_or_abort("bn254 g1 data not found at " + path.string() +
238 " and bb does not automatically download in this context." +
239 " Run barretenberg/crs/bootstrap.sh to download.");
240 } else if (!allow_download) {
241 throw_or_abort(format("bn254 g1 data had ",
242 compressed_points,
243 " points and ",
244 num_points,
245 " were requested but download not allowed in this context"));
246 }
247
248 // Double-check after acquiring lock (another process may have downloaded while we waited)
249 uncompressed_points = get_file_size(uncompressed_path) / UNCOMPRESSED_POINT_SIZE;
250 if (uncompressed_points >= num_points) {
251 return read_uncompressed_g1_points(uncompressed_path, num_points);
252 }
253 compressed_points = get_file_size(compressed_path) / COMPRESSED_POINT_SIZE;
254 if (compressed_points >= num_points) {
255 auto data = read_file(compressed_path, num_points * COMPRESSED_POINT_SIZE);
256 auto points = decompress_g1_points(data, num_points);
257 write_uncompressed_g1_points(points, uncompressed_path);
258 return points;
259 }
260
261 // 3. Download compressed, decompress, cache uncompressed
262 vinfo("downloading bn254 crs...");
263 auto data = download_bn254_g1_data(num_points, primary_url, fallback_url);
264 write_file(compressed_path, data);
265 auto points = decompress_g1_points(data, num_points);
266 write_uncompressed_g1_points(points, uncompressed_path);
267 vinfo("cached uncompressed bn254 crs at ", uncompressed_path);
268 return points;
269}
270
271// Default overload using production URLs
272std::vector<g1::affine_element> get_bn254_g1_data(const std::filesystem::path& path,
273 size_t num_points,
274 bool allow_download)
275{
276 return get_bn254_g1_data(path, num_points, allow_download, CRS_PRIMARY_URL, CRS_FALLBACK_URL);
277}
278
279// Loads the canonical 128-byte serialization of [x]_2 from disk and verifies it against the pinned
280// SHA-256 and the BN254 G2 prime-order subgroup.
281g2::affine_element get_bn254_g2_data(const std::filesystem::path& path)
282{
283 constexpr size_t G2_BYTES = 128;
284 auto g2_path = path / "bn254_g2.dat";
285 if (get_file_size(g2_path) != G2_BYTES) {
286 throw_or_abort("bn254 g2 data not found at " + path.string() +
287 " or has wrong size. Run barretenberg/crs/bootstrap.sh to provision.");
288 }
289 auto data = read_file(g2_path, G2_BYTES);
290 auto point = from_buffer<g2::affine_element>(data.data());
291
292 // Reject the point at infinity: it is a member of every subgroup (so subgroup check passes)
293 // but `e(−W, O) = 1` for every W, which collapses the KZG verifier's pairing check and lets
294 // a malicious prover forge arbitrary openings.
295 if (point.is_point_at_infinity()) {
296 throw_or_abort("bn254 g2 cannot be the point at infinity");
297 }
298
299 // Verify SHA-256 hash of the raw bytes matches the pinned constant for canonical [x]_2.
300 auto hash = bb::crypto::sha256(std::span<const uint8_t>(data.data(), data.size()));
302 throw_or_abort("bn254 g2 SHA-256 mismatch: payload does not match the canonical [x]_2");
303 }
304 if (!point.is_in_prime_subgroup()) {
305 throw_or_abort("bn254 g2 deserialized to a point outside the prime-order subgroup");
306 }
307 return point;
308}
309
310} // namespace bb
#define BB_BENCH_NAME(name)
Definition bb_bench.hpp:264
std::string format(Args... args)
Definition log.hpp:23
#define vinfo(...)
Definition log.hpp:94
const std::vector< MemoryValue > data
ssize_t offset
Definition engine.cpp:62
Sha256Hash sha256(const ByteContainer &input)
SHA-256 hash function (FIPS 180-4)
Definition sha256.cpp:150
const size_t num_points
constexpr uint256_t BN254_G1_FIRST_ELEMENT_COMPRESSED
Compressed form of the first G1 element (generator point).
std::vector< uint8_t > http_download(const std::string &url, size_t start_byte=0, size_t end_byte=0)
Download data from a URL with optional Range header support.
constexpr std::array< uint8_t, 32 > BN254_G2_ELEMENT_SHA256
SHA-256 hash of BN254_G2_ELEMENT_BYTES.
constexpr uint256_t BN254_G1_SECOND_ELEMENT_COMPRESSED
Compressed form of the second G1 element from the trusted setup.
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
std::vector< g1::affine_element > get_bn254_g1_data(const std::filesystem::path &path, size_t num_points, bool allow_download, const std::string &primary_url, const std::string &fallback_url)
std::vector< uint8_t > read_file(const std::string &filename, size_t bytes=0)
Definition file_io.hpp:31
g2::affine_element get_bn254_g2_data(const std::filesystem::path &path)
void write_file(const std::string &filename, std::span< const uint8_t > data)
Definition file_io.hpp:101
void parallel_for(size_t num_iterations, const std::function< void(size_t)> &func)
Definition thread.cpp:111
size_t get_file_size(std::string const &filename)
Definition file_io.hpp:19
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::string to_string(bb::avm2::ValueTag tag)
std::vector< uint8_t > to_buffer(T const &value)
void throw_or_abort(std::string const &err)