Skip to content

Commit cef15ec

Browse files
feat: add Cloudflare R2 as primary CRS host with S3 fallback
Adds a new Cloudflare R2 endpoint (crs.aztec-cdn.foundation) as the primary CRS download source with automatic fallback to the existing AWS S3 bucket (crs.aztec.network) on failure. This improves reliability and reduces costs since R2 has no egress fees. Changes: - C++: Added fallback logic in get_bn254_crs.cpp with HTTPS support - TypeScript: Added fetchWithFallback helper in net_crs.ts - Shell: Added download_with_fallback function in bootstrap.sh and download_bb_crs.sh - Tests: Added fallback tests for both C++ and TypeScript Resolves AztecProtocol/barretenberg#1609
1 parent afe8981 commit cef15ec

File tree

9 files changed

+210
-33
lines changed

9 files changed

+210
-33
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ inline constexpr g1::affine_element BN254_G1_FIRST_ELEMENT = g1::affine_one;
1515
/**
1616
* @brief Expected second G1 element from BN254 CRS
1717
* @details This is the second point in the BN254 CRS, corresponding to tau * G where tau is the secret from the
18-
* trusted setup. Reference: http://crs.aztec.network/g1.dat (bytes 64-127)
18+
* trusted setup. Reference: https://crs.aztec-cdn.foundation/g1.dat (bytes 64-127)
1919
*/
2020
inline g1::affine_element get_bn254_g1_second_element()
2121
{
@@ -32,7 +32,7 @@ inline g1::affine_element get_bn254_g1_second_element()
3232
/**
3333
* @brief Reference BN254 G2 element from the trusted setup CRS
3434
* @details This is the single G2 point used in the BN254 CRS for verification.
35-
* Reference: http://crs.aztec.network/g2.dat
35+
* Reference: https://crs.aztec-cdn.foundation/g2.dat
3636
*/
3737
inline g2::affine_element get_bn254_g2_crs_element()
3838
{

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
#include "barretenberg/common/serialize.hpp"
33
#include "barretenberg/ecc/curves/bn254/bn254.hpp"
44
#include "barretenberg/ecc/curves/bn254/pairing.hpp"
5+
#include "barretenberg/srs/factories/bn254_crs_data.hpp"
6+
#include "barretenberg/srs/factories/get_bn254_crs.hpp"
57
#include "barretenberg/srs/factories/mem_bn254_crs_factory.hpp"
68
#include "barretenberg/srs/factories/mem_grumpkin_crs_factory.hpp"
79
#include "barretenberg/srs/factories/native_crs_factory.hpp"
@@ -100,3 +102,23 @@ TEST(CrsFactory, grumpkin)
100102
ASSERT_ANY_THROW(check_grumpkin_consistency(temp_crs_path, 1, /*allow_download=*/false));
101103
check_grumpkin_consistency(temp_crs_path, 1, /*allow_download=*/true);
102104
}
105+
106+
TEST(CrsFactory, Bn254Fallback)
107+
{
108+
// Test that fallback works when primary URL fails
109+
const std::filesystem::path& temp_crs_path = "barretenberg_srs_test_crs_bn254_fallback";
110+
fs::remove_all(temp_crs_path);
111+
fs::create_directories(temp_crs_path);
112+
113+
// Use a bad primary URL that will fail, forcing fallback to the real S3 URL
114+
std::string bad_primary = "http://nonexistent.invalid/g1.dat";
115+
std::string good_fallback = "http://crs.aztec.network/g1.dat";
116+
117+
// This should succeed by falling back to the working URL
118+
auto points = bb::get_bn254_g1_data(temp_crs_path, 1, /*allow_download=*/true, bad_primary, good_fallback);
119+
EXPECT_EQ(points.size(), 1);
120+
// Verify the downloaded point matches the expected first element
121+
EXPECT_EQ(points[0], bb::srs::BN254_G1_FIRST_ELEMENT);
122+
123+
fs::remove_all(temp_crs_path);
124+
}

barretenberg/cpp/src/barretenberg/srs/factories/get_bn254_crs.cpp

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,25 @@
88
#include "http_download.hpp"
99

1010
namespace {
11-
std::vector<uint8_t> download_bn254_g1_data(size_t num_points)
11+
// Primary CRS URL (Cloudflare R2)
12+
constexpr const char* CRS_PRIMARY_URL = "https://crs.aztec-cdn.foundation/g1.dat";
13+
// Fallback CRS URL (AWS S3)
14+
constexpr const char* CRS_FALLBACK_URL = "http://crs.aztec.network/g1.dat";
15+
16+
std::vector<uint8_t> download_bn254_g1_data(size_t num_points,
17+
const std::string& primary_url,
18+
const std::string& fallback_url)
1219
{
1320
size_t g1_end = (num_points * sizeof(bb::g1::affine_element)) - 1;
1421

15-
// Download via HTTP with Range header
16-
auto data = bb::srs::http_download("http://crs.aztec.network/g1.dat", 0, g1_end);
22+
// Try primary URL first
23+
std::vector<uint8_t> data;
24+
try {
25+
data = bb::srs::http_download(primary_url, 0, g1_end);
26+
} catch (const std::exception& e) {
27+
vinfo("Primary CRS download failed: ", e.what(), ". Trying fallback...");
28+
data = bb::srs::http_download(fallback_url, 0, g1_end);
29+
}
1730

1831
if (data.size() < sizeof(bb::g1::affine_element)) {
1932
throw_or_abort("Downloaded g1 data is too small");
@@ -38,9 +51,13 @@ std::vector<uint8_t> download_bn254_g1_data(size_t num_points)
3851
} // namespace
3952

4053
namespace bb {
54+
55+
// Main implementation with configurable URLs
4156
std::vector<g1::affine_element> get_bn254_g1_data(const std::filesystem::path& path,
4257
size_t num_points,
43-
bool allow_download)
58+
bool allow_download,
59+
const std::string& primary_url,
60+
const std::string& fallback_url)
4461
{
4562
std::filesystem::create_directories(path);
4663

@@ -84,7 +101,7 @@ std::vector<g1::affine_element> get_bn254_g1_data(const std::filesystem::path& p
84101
}
85102

86103
vinfo("downloading bn254 crs...");
87-
auto data = download_bn254_g1_data(num_points);
104+
auto data = download_bn254_g1_data(num_points, primary_url, fallback_url);
88105
write_file(g1_path, data);
89106

90107
auto points = std::vector<g1::affine_element>(num_points);
@@ -94,4 +111,12 @@ std::vector<g1::affine_element> get_bn254_g1_data(const std::filesystem::path& p
94111
return points;
95112
}
96113

114+
// Default overload using production URLs
115+
std::vector<g1::affine_element> get_bn254_g1_data(const std::filesystem::path& path,
116+
size_t num_points,
117+
bool allow_download)
118+
{
119+
return get_bn254_g1_data(path, num_points, allow_download, CRS_PRIMARY_URL, CRS_FALLBACK_URL);
120+
}
121+
97122
} // namespace bb

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,13 @@ namespace bb {
99
std::vector<g1::affine_element> get_bn254_g1_data(const std::filesystem::path& path,
1010
size_t num_points,
1111
bool allow_download = true);
12+
13+
// Overload with custom URLs for testing fallback behavior
14+
std::vector<g1::affine_element> get_bn254_g1_data(const std::filesystem::path& path,
15+
size_t num_points,
16+
bool allow_download,
17+
const std::string& primary_url,
18+
const std::string& fallback_url);
19+
1220
g2::affine_element get_bn254_g2_data(const std::filesystem::path& path, bool allow_download = true);
1321
} // namespace bb

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,13 @@ inline std::vector<uint8_t> http_download([[maybe_unused]] const std::string& ur
5252
throw_or_abort("Invalid URL format: " + url);
5353
}
5454

55+
std::string protocol = url.substr(0, proto_end);
5556
std::string host = url.substr(host_start, path_start - host_start);
5657
std::string path = url.substr(path_start);
5758

58-
// Create HTTP client (non-SSL)
59-
httplib::Client cli(("http://" + host).c_str());
59+
// Create HTTP client (supports both http and https)
60+
std::string base_url = protocol + "://" + host;
61+
httplib::Client cli(base_url.c_str());
6062
cli.set_follow_location(true);
6163
cli.set_connection_timeout(30);
6264
cli.set_read_timeout(60);

barretenberg/crs/bootstrap.sh

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,29 @@ shift || true
99
# 2^25 points + 1 because the first is the generator, *64 bytes per point, -1 because Range is inclusive.
1010
# We make the file read only to ensure no test can attempt to grow it any larger. 2^25 is already huge...
1111
# TODO: Make bb just download and append/overwrite required range, then it becomes idempotent.
12+
13+
# Primary CRS host (Cloudflare R2)
14+
CRS_PRIMARY_HOST="https://crs.aztec-cdn.foundation"
15+
# Fallback CRS host (AWS S3)
16+
CRS_FALLBACK_HOST="https://crs.aztec.network"
17+
18+
# Download with fallback: try primary first, then fallback on failure
19+
download_with_fallback() {
20+
local output="$1"
21+
local file="$2"
22+
local range_header="${3:-}"
23+
24+
local curl_args=(-s -f -o "$output")
25+
if [ -n "$range_header" ]; then
26+
curl_args+=(-H "Range: $range_header")
27+
fi
28+
29+
if ! curl "${curl_args[@]}" "${CRS_PRIMARY_HOST}/${file}" 2>/dev/null; then
30+
echo "Primary CRS host failed, trying fallback..."
31+
curl "${curl_args[@]}" "${CRS_FALLBACK_HOST}/${file}"
32+
fi
33+
}
34+
1235
function build {
1336
crs_path=$HOME/.bb-crs
1437
crs_size=$((2**25+1))
@@ -18,12 +41,11 @@ function build {
1841
if [ ! -f "$g1" ] || [ $(stat -c%s "$g1") -lt $crs_size_bytes ]; then
1942
echo "Downloading crs of size: ${crs_size} ($((crs_size_bytes/(1024*1024)))MB)"
2043
mkdir -p $crs_path
21-
curl -s -H "Range: bytes=0-$((crs_size_bytes-1))" -o $g1 \
22-
https://crs.aztec.network/g1.dat
44+
download_with_fallback "$g1" "g1.dat" "bytes=0-$((crs_size_bytes-1))"
2345
chmod a-w $crs_path/bn254_g1.dat
2446
fi
2547
if [ ! -f "$g2" ]; then
26-
curl -s https://crs.aztec.network/g2.dat -o $g2
48+
download_with_fallback "$g2" "g2.dat"
2749
fi
2850

2951
# TODO: This grumpkin CRS in S3 still has the 28 byte header on it. Remove.
@@ -33,8 +55,7 @@ function build {
3355
gg1=$crs_path/grumpkin_g1.flat.dat
3456
if [ ! -f "$gg1" ] || [ $(stat -c%s "$gg1") -lt $crs_size_bytes ]; then
3557
echo "Downloading grumpkin crs of size: ${crs_size} ($((crs_size_bytes/(1024*1024)))MB)"
36-
curl -s -H "Range: bytes=0-$((crs_size_bytes-1))" -o $gg1 \
37-
https://crs.aztec.network/grumpkin_g1.dat
58+
download_with_fallback "$gg1" "grumpkin_g1.dat" "bytes=0-$((crs_size_bytes-1))"
3859
fi
3960
}
4061

barretenberg/scripts/download_bb_crs.sh

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,29 @@ set -eu
66
# 2^25 points + 1 because the first is the generator, *64 bytes per point, -1 because Range is inclusive.
77
# We make the file read only to ensure no test can attempt to grow it any larger. 2^25 is already huge...
88
# TODO: Make bb just download and append/overwrite required range, then it becomes idempotent.
9+
10+
# Primary CRS host (Cloudflare R2)
11+
CRS_PRIMARY_HOST="https://crs.aztec-cdn.foundation"
12+
# Fallback CRS host (AWS S3)
13+
CRS_FALLBACK_HOST="https://crs.aztec.network"
14+
15+
# Download with fallback: try primary first, then fallback on failure
16+
download_with_fallback() {
17+
local output="$1"
18+
local file="$2"
19+
local range_header="${3:-}"
20+
21+
local curl_args=(-s -f -o "$output")
22+
if [ -n "$range_header" ]; then
23+
curl_args+=(-H "Range: $range_header")
24+
fi
25+
26+
if ! curl "${curl_args[@]}" "${CRS_PRIMARY_HOST}/${file}" 2>/dev/null; then
27+
echo "Primary CRS host failed, trying fallback..."
28+
curl "${curl_args[@]}" "${CRS_FALLBACK_HOST}/${file}"
29+
fi
30+
}
31+
932
crs_path=$HOME/.bb-crs
1033
crs_size=$((2**25+1))
1134
crs_size_bytes=$((crs_size*64))
@@ -14,12 +37,11 @@ g2=$crs_path/bn254_g2.dat
1437
if [ ! -f "$g1" ] || [ $(stat -c%s "$g1") -lt $crs_size_bytes ]; then
1538
echo "Downloading crs of size: ${crs_size} ($((crs_size_bytes/(1024*1024)))MB)"
1639
mkdir -p $crs_path
17-
curl -s -H "Range: bytes=0-$((crs_size_bytes-1))" -o $g1 \
18-
https://crs.aztec.network/g1.dat
40+
download_with_fallback "$g1" "g1.dat" "bytes=0-$((crs_size_bytes-1))"
1941
chmod a-w $crs_path/bn254_g1.dat
2042
fi
2143
if [ ! -f "$g2" ]; then
22-
curl -s https://crs.aztec.network/g2.dat -o $g2
44+
download_with_fallback "$g2" "g2.dat"
2345
fi
2446

2547
# TODO: This grumpkin CRS in S3 still has the 28 byte header on it. Remove.
@@ -29,6 +51,5 @@ crs_size_bytes=$((crs_size*64))
2951
gg1=$crs_path/grumpkin_g1.flat.dat
3052
if [ ! -f "$gg1" ] || [ $(stat -c%s "$gg1") -lt $crs_size_bytes ]; then
3153
echo "Downloading grumpkin crs of size: ${crs_size} ($((crs_size_bytes/(1024*1024)))MB)"
32-
curl -s -H "Range: bytes=0-$((crs_size_bytes-1))" -o $gg1 \
33-
https://crs.aztec.network/grumpkin_g1.dat
54+
download_with_fallback "$gg1" "grumpkin_g1.dat" "bytes=0-$((crs_size_bytes-1))"
3455
fi
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { NetCrs, fetchWithFallback } from './net_crs.js';
2+
3+
// Expected first G1 point from BN254 CRS (generator point with x=1, y=2 in big-endian)
4+
const BN254_G1_FIRST_ELEMENT = new Uint8Array([
5+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
6+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,
7+
]);
8+
9+
describe('NetCrs', () => {
10+
it('should download CRS data from primary host', async () => {
11+
const crs = new NetCrs(1);
12+
await crs.init();
13+
14+
const g1Data = crs.getG1Data();
15+
expect(g1Data.length).toBe(64); // 1 point * 64 bytes
16+
17+
// Verify first point matches expected generator
18+
expect(g1Data).toEqual(BN254_G1_FIRST_ELEMENT);
19+
}, 30000);
20+
21+
it('should download G2 data', async () => {
22+
const crs = new NetCrs(1);
23+
await crs.init();
24+
25+
const g2Data = crs.getG2Data();
26+
expect(g2Data.length).toBe(128); // G2 point is 128 bytes
27+
}, 30000);
28+
});
29+
30+
describe('fetchWithFallback', () => {
31+
it('should fallback to secondary URL when primary fails', async () => {
32+
const badPrimaryUrl = 'https://nonexistent.invalid/g1.dat';
33+
const goodFallbackUrl = 'https://crs.aztec.network/g1.dat';
34+
const options: RequestInit = {
35+
headers: {
36+
Range: 'bytes=0-63',
37+
},
38+
};
39+
40+
const response = await fetchWithFallback(badPrimaryUrl, goodFallbackUrl, options);
41+
expect(response.ok || response.status === 206).toBe(true);
42+
43+
const data = new Uint8Array(await response.arrayBuffer());
44+
expect(data.length).toBe(64);
45+
expect(data).toEqual(BN254_G1_FIRST_ELEMENT);
46+
}, 30000);
47+
});

barretenberg/ts/src/crs/net_crs.ts

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,30 @@
11
import { retry, makeBackoff } from '../retry/index.js';
2+
3+
// Primary CRS host (Cloudflare R2)
4+
const CRS_PRIMARY_HOST = 'https://crs.aztec-cdn.foundation';
5+
// Fallback CRS host (AWS S3)
6+
const CRS_FALLBACK_HOST = 'https://crs.aztec.network';
7+
8+
/**
9+
* Fetches data from primary URL, falling back to secondary on failure
10+
* @internal Exported for testing
11+
*/
12+
export async function fetchWithFallback(
13+
primaryUrl: string,
14+
fallbackUrl: string,
15+
options: RequestInit,
16+
): Promise<Response> {
17+
try {
18+
const response = await fetch(primaryUrl, options);
19+
if (response.ok || response.status === 206) {
20+
return response;
21+
}
22+
throw new Error(`HTTP ${response.status}`);
23+
} catch {
24+
return await fetch(fallbackUrl, options);
25+
}
26+
}
27+
228
/**
329
* Downloader for CRS from the web or local.
430
*/
@@ -76,14 +102,14 @@ export class NetCrs {
76102
}
77103

78104
const g1End = this.numPoints * 64 - 1;
105+
const options: RequestInit = {
106+
headers: {
107+
Range: `bytes=0-${g1End}`,
108+
},
109+
cache: 'force-cache',
110+
};
79111
return await retry(
80-
() =>
81-
fetch('https://crs.aztec.network/g1.dat', {
82-
headers: {
83-
Range: `bytes=0-${g1End}`,
84-
},
85-
cache: 'force-cache',
86-
}),
112+
() => fetchWithFallback(`${CRS_PRIMARY_HOST}/g1.dat`, `${CRS_FALLBACK_HOST}/g1.dat`, options),
87113
makeBackoff([5, 5, 5]),
88114
);
89115
}
@@ -92,11 +118,11 @@ export class NetCrs {
92118
* Fetches the appropriate range of points from a remote source
93119
*/
94120
private async fetchG2Data(): Promise<Response> {
121+
const options: RequestInit = {
122+
cache: 'force-cache',
123+
};
95124
return await retry(
96-
() =>
97-
fetch('https://crs.aztec.network/g2.dat', {
98-
cache: 'force-cache',
99-
}),
125+
() => fetchWithFallback(`${CRS_PRIMARY_HOST}/g2.dat`, `${CRS_FALLBACK_HOST}/g2.dat`, options),
100126
makeBackoff([5, 5, 5]),
101127
);
102128
}
@@ -153,12 +179,17 @@ export class NetGrumpkinCrs {
153179
}
154180

155181
const g1End = this.numPoints * 64 - 1;
156-
157-
return await fetch('https://crs.aztec.network/grumpkin_g1.dat', {
182+
const options: RequestInit = {
158183
headers: {
159184
Range: `bytes=0-${g1End}`,
160185
},
161186
cache: 'force-cache',
162-
});
187+
};
188+
189+
return await fetchWithFallback(
190+
`${CRS_PRIMARY_HOST}/grumpkin_g1.dat`,
191+
`${CRS_FALLBACK_HOST}/grumpkin_g1.dat`,
192+
options,
193+
);
163194
}
164195
}

0 commit comments

Comments
 (0)