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
82void verify_bn254_crs_integrity(const std::vector<uint8_t>& data)
83{
84 size_t num_full_chunks = data.size() / bb::srs::SRS_CHUNK_SIZE_BYTES;
85 size_t chunks_to_verify = std::min(num_full_chunks, static_cast<size_t>(bb::srs::SRS_NUM_FULL_CHUNKS));
86
87 // Sentinel value means "no failure found yet"
88 const size_t sentinel = bb::srs::SRS_NUM_CHUNKS;
89 std::atomic<size_t> failed_chunk{ sentinel };
90
91 // Verify all complete chunks in parallel
92 if (chunks_to_verify > 0) {
93 bb::parallel_for([&](const bb::ThreadChunk& tc) {
94 for (size_t i : tc.range(chunks_to_verify)) {
95 // Early exit if another thread already found a mismatch
96 if (failed_chunk.load(std::memory_order_relaxed) < sentinel) {
97 return;
98 }
99 size_t offset = i * bb::srs::SRS_CHUNK_SIZE_BYTES;
100 auto chunk = std::span<const uint8_t>(data.data() + offset, bb::srs::SRS_CHUNK_SIZE_BYTES);
101 auto hash = bb::crypto::sha256(chunk);
102 if (hash != bb::srs::BN254_G1_CHUNK_HASHES[i]) {
103 size_t expected = sentinel;
104 failed_chunk.compare_exchange_strong(expected, i, std::memory_order_relaxed);
105 }
106 }
107 });
108 }
109
110 // Verify partial last chunk (e.g. the 32-byte tail of the full CRS)
111 size_t tail_offset = chunks_to_verify * bb::srs::SRS_CHUNK_SIZE_BYTES;
112 size_t tail_size = data.size() - tail_offset;
113 if (tail_size > 0 && chunks_to_verify < bb::srs::SRS_NUM_CHUNKS) {
114 auto tail = std::span<const uint8_t>(data.data() + tail_offset, tail_size);
115 auto hash = bb::crypto::sha256(tail);
116 if (hash != bb::srs::BN254_G1_CHUNK_HASHES[chunks_to_verify]) {
117 size_t expected = sentinel;
118 failed_chunk.compare_exchange_strong(expected, chunks_to_verify, std::memory_order_relaxed);
119 }
120 }
121
122 size_t bad = failed_chunk.load();
123 if (bad < sentinel) {
124 size_t offset = bad * bb::srs::SRS_CHUNK_SIZE_BYTES;
125 throw_or_abort("CRS integrity check failed: SHA-256 mismatch at chunk " + std::to_string(bad) + " (bytes " +
126 std::to_string(offset) + "+)");
127 }
128
129 vinfo("verified ", chunks_to_verify + (tail_size > 0 ? 1 : 0), " BN254 G1 CRS chunks via SHA-256");
130}
131
135std::vector<bb::g1::affine_element> decompress_g1_points(const std::vector<uint8_t>& data, size_t num_points)
136{
137 std::vector<bb::g1::affine_element> points(num_points);
139 for (auto i : chunk.range(num_points)) {
140 uint256_t compressed = from_buffer<uint256_t>(data, i * COMPRESSED_POINT_SIZE);
141 points[i] = bb::g1::affine_element::from_compressed(compressed);
142 }
143 });
144 return points;
145}
146
147std::vector<uint8_t> download_bn254_g1_data(size_t num_points,
148 const std::string& primary_url,
149 const std::string& fallback_url)
150{
151 // Round up to chunk boundary so every downloaded byte is hash-verified
152 size_t download_points = round_up_to_chunk_boundary(num_points);
153 size_t g1_end = (download_points * COMPRESSED_POINT_SIZE) - 1;
154
155 // Try primary URL first, with fallback on failure.
156 // Note: WASM is compiled with -fno-exceptions, so try/catch is not available.
157 // In practice, WASM never calls this function - it initializes CRS via srs_init_srs from JavaScript.
158 std::vector<uint8_t> data;
159#ifndef __wasm__
160 try {
161 data = bb::srs::http_download(primary_url, 0, g1_end);
162 } catch (const std::exception& e) {
163 vinfo("Primary CRS download failed: ", e.what(), ". Trying fallback...");
164 data = bb::srs::http_download(fallback_url, 0, g1_end);
165 }
166#else
167 // WASM fallback: just try primary (will abort on failure)
168 data = bb::srs::http_download(primary_url, 0, g1_end);
169 static_cast<void>(fallback_url);
170#endif
171
172 if (data.size() < COMPRESSED_POINT_SIZE) {
173 throw_or_abort("Downloaded g1 data is too small");
174 }
175
176 // Quick sanity check: verify the first two G1 points match expected values
177 auto first = from_buffer<uint256_t>(data, 0);
179 throw_or_abort("Downloaded BN254 G1 CRS first element does not match expected point.");
180 }
181
182 if (data.size() >= 2 * COMPRESSED_POINT_SIZE) {
183 auto second = from_buffer<uint256_t>(data, COMPRESSED_POINT_SIZE);
185 throw_or_abort("Downloaded BN254 G1 CRS second element does not match expected point.");
186 }
187 }
188
189 // Full integrity verification: SHA-256 chunk hashes in parallel
190 verify_bn254_crs_integrity(data);
191
192 return data;
193}
194
195} // namespace
196
197namespace bb {
198
199// Main implementation with configurable URLs
200std::vector<g1::affine_element> get_bn254_g1_data(const std::filesystem::path& path,
201 size_t num_points,
202 bool allow_download,
203 const std::string& primary_url,
204 const std::string& fallback_url)
205{
206 BB_BENCH_NAME("get_bn254_g1_data");
207 std::filesystem::create_directories(path);
208
209 auto uncompressed_path = path / "bn254_g1.dat";
210 auto compressed_path = path / "bn254_g1_compressed.dat";
211 auto lock_path = path / "crs.lock";
212 // Acquire exclusive lock to prevent simultaneous downloads
213 FileLockGuard lock(lock_path.string());
214
215 // 1. Prefer cached uncompressed (fastest: parallel from_buffer, ~0.3s for 2^20 points)
216 size_t uncompressed_points = get_file_size(uncompressed_path) / UNCOMPRESSED_POINT_SIZE;
217 if (uncompressed_points >= num_points) {
218 vinfo("using cached uncompressed bn254 crs with ", uncompressed_points, " points at ", uncompressed_path);
219 return read_uncompressed_g1_points(uncompressed_path, num_points);
220 }
221
222 // 2. Fall back to compressed on disk: decompress and cache uncompressed
223 size_t compressed_points = get_file_size(compressed_path) / COMPRESSED_POINT_SIZE;
224 if (compressed_points >= num_points) {
225 vinfo("decompressing cached compressed bn254 crs (", compressed_points, " points)...");
226 auto data = read_file(compressed_path, num_points * COMPRESSED_POINT_SIZE);
227 // BB_VERIFY_CRS=1 opts in to per-load verification of cached compressed bytes against the
228 // compiled-in chunk hashes. Off by default so cache-hit loads stay cheap.
229 if (std::getenv("BB_VERIFY_CRS") != nullptr) {
230 verify_bn254_crs_integrity(data);
231 }
232 auto points = decompress_g1_points(data, num_points);
233 write_uncompressed_g1_points(points, uncompressed_path);
234 vinfo("cached uncompressed bn254 crs at ", uncompressed_path);
235 return points;
236 }
237
238 if (!allow_download && compressed_points == 0) {
239 throw_or_abort("bn254 g1 data not found at " + path.string() +
240 " and bb does not automatically download in this context." +
241 " Run barretenberg/crs/bootstrap.sh to download.");
242 } else if (!allow_download) {
243 throw_or_abort(format("bn254 g1 data had ",
244 compressed_points,
245 " points and ",
246 num_points,
247 " were requested but download not allowed in this context"));
248 }
249
250 // Double-check after acquiring lock (another process may have downloaded while we waited)
251 uncompressed_points = get_file_size(uncompressed_path) / UNCOMPRESSED_POINT_SIZE;
252 if (uncompressed_points >= num_points) {
253 return read_uncompressed_g1_points(uncompressed_path, num_points);
254 }
255 compressed_points = get_file_size(compressed_path) / COMPRESSED_POINT_SIZE;
256 if (compressed_points >= num_points) {
257 auto data = read_file(compressed_path, num_points * COMPRESSED_POINT_SIZE);
258 if (std::getenv("BB_VERIFY_CRS") != nullptr) {
259 verify_bn254_crs_integrity(data);
260 }
261 auto points = decompress_g1_points(data, num_points);
262 write_uncompressed_g1_points(points, uncompressed_path);
263 return points;
264 }
265
266 // 3. Download compressed, decompress, cache uncompressed
267 vinfo("downloading bn254 crs...");
268 auto data = download_bn254_g1_data(num_points, primary_url, fallback_url);
269 write_file(compressed_path, data);
270 auto points = decompress_g1_points(data, num_points);
271 write_uncompressed_g1_points(points, uncompressed_path);
272 vinfo("cached uncompressed bn254 crs at ", uncompressed_path);
273 return points;
274}
275
276// Default overload using production URLs
277std::vector<g1::affine_element> get_bn254_g1_data(const std::filesystem::path& path,
278 size_t num_points,
279 bool allow_download)
280{
281 return get_bn254_g1_data(path, num_points, allow_download, CRS_PRIMARY_URL, CRS_FALLBACK_URL);
282}
283
284// Loads the canonical 128-byte serialization of [x]_2 from disk and verifies it against the pinned
285// SHA-256 and the BN254 G2 prime-order subgroup.
286g2::affine_element get_bn254_g2_data(const std::filesystem::path& path)
287{
288 constexpr size_t G2_BYTES = 128;
289 auto g2_path = path / "bn254_g2.dat";
290 if (get_file_size(g2_path) != G2_BYTES) {
291 throw_or_abort("bn254 g2 data not found at " + path.string() +
292 " or has wrong size. Run barretenberg/crs/bootstrap.sh to provision.");
293 }
294 auto data = read_file(g2_path, G2_BYTES);
295 auto point = from_buffer<g2::affine_element>(data.data());
296
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 }
303
304 // Verify SHA-256 hash of the raw bytes matches the pinned constant for canonical [x]_2.
305 auto hash = bb::crypto::sha256(std::span<const uint8_t>(data.data(), data.size()));
307 throw_or_abort("bn254 g2 SHA-256 mismatch: payload does not match the canonical [x]_2");
308 }
309 if (!point.is_in_prime_subgroup()) {
310 throw_or_abort("bn254 g2 deserialized to a point outside the prime-order subgroup");
311 }
312 return point;
313}
314
315} // 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
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:112
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::byte * data
std::vector< uint8_t > to_buffer(T const &value)
void throw_or_abort(std::string const &err)