Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
Empty file.
1 change: 1 addition & 0 deletions barretenberg/cpp/src/barretenberg/crypto/sha256/sha256.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -194,5 +194,6 @@ template Sha256Hash sha256<std::vector<uint8_t>>(const std::vector<uint8_t>& inp
template Sha256Hash sha256<std::array<uint8_t, 32>>(const std::array<uint8_t, 32>& input);
template Sha256Hash sha256<std::string>(const std::string& input);
template Sha256Hash sha256<std::span<uint8_t>>(const std::span<uint8_t>& input);
template Sha256Hash sha256<std::span<const uint8_t>>(const std::span<const uint8_t>& input);

} // namespace bb::crypto
347 changes: 347 additions & 0 deletions barretenberg/cpp/src/barretenberg/srs/factories/bn254_crs_hashes.hpp

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "barretenberg/ecc/curves/bn254/bn254.hpp"
#include "barretenberg/ecc/curves/bn254/pairing.hpp"
#include "barretenberg/srs/factories/bn254_crs_data.hpp"
#include "barretenberg/srs/factories/bn254_crs_hashes.hpp"
#include "barretenberg/srs/factories/get_bn254_crs.hpp"
#include "barretenberg/srs/factories/mem_bn254_crs_factory.hpp"
#include "barretenberg/srs/factories/mem_grumpkin_crs_factory.hpp"
Expand Down Expand Up @@ -122,3 +123,21 @@ TEST(CrsFactory, Bn254Fallback)

fs::remove_all(temp_crs_path);
}

TEST(CrsFactory, Bn254HashVerification)
{
// Verify whatever CRS data we have on disk against the embedded chunk hashes.
auto g1_path = bb::srs::bb_crs_path() / "bn254_g1.dat";
size_t file_size = get_file_size(g1_path);

// Round down to a whole number of 8MB chunks (the verify function requires alignment).
size_t verifiable_size = (file_size / bb::srs::CRS_HASH_CHUNK_SIZE) * bb::srs::CRS_HASH_CHUNK_SIZE;
ASSERT_GT(verifiable_size, 0) << "Need at least one 8MB chunk of CRS data on disk";

auto data = read_file(g1_path, verifiable_size);
EXPECT_NO_THROW(bb::srs::verify_bn254_crs_integrity(data));

// Corrupt a byte and verify that hash check catches it
data[100] ^= 0xFF;
EXPECT_ANY_THROW(bb::srs::verify_bn254_crs_integrity(data));
}
28 changes: 19 additions & 9 deletions barretenberg/cpp/src/barretenberg/srs/factories/get_bn254_crs.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
#include "get_bn254_crs.hpp"
#include "barretenberg/api/file_io.hpp"
#include <algorithm>
#include "barretenberg/common/flock.hpp"
#include "barretenberg/common/serialize.hpp"
#include "barretenberg/ecc/curves/bn254/g1.hpp"
#include "barretenberg/ecc/curves/bn254/g2.hpp"
#include "bn254_crs_data.hpp"
#include "bn254_crs_hashes.hpp"
#include "http_download.hpp"

namespace {
Expand All @@ -17,7 +19,19 @@ std::vector<uint8_t> download_bn254_g1_data(size_t num_points,
const std::string& primary_url,
const std::string& fallback_url)
{
size_t g1_end = (num_points * sizeof(bb::g1::affine_element)) - 1;
// Round up download size to next 8MB chunk boundary so every downloaded chunk
// can be fully verified against embedded SHA256 hashes.
// Cap at 256 full chunks to avoid requesting past end-of-file (the full CRS has
// 256 full 8MB chunks + a 64-byte remainder that can't fill another chunk).
constexpr size_t points_per_chunk = bb::srs::CRS_HASH_CHUNK_SIZE / sizeof(bb::g1::affine_element);
constexpr size_t max_aligned_points = bb::srs::CRS_NUM_FULL_CHUNKS * points_per_chunk;
size_t aligned_points = ((num_points + points_per_chunk - 1) / points_per_chunk) * points_per_chunk;
if (aligned_points > max_aligned_points) {
aligned_points = max_aligned_points;
}
// Request enough bytes for whichever is larger: the chunk-aligned amount or the actual request.
size_t download_points = std::max(aligned_points, num_points);
size_t g1_end = (download_points * sizeof(bb::g1::affine_element)) - 1;

// Try primary URL first, with fallback on failure.
// Note: WASM is compiled with -fno-exceptions, so try/catch is not available.
Expand All @@ -40,19 +54,15 @@ std::vector<uint8_t> download_bn254_g1_data(size_t num_points,
throw_or_abort("Downloaded g1 data is too small");
}

// Verify first element matches our expected point.
// Verify first element matches the expected generator point (quick sanity check for all download sizes).
auto first_element = from_buffer<bb::g1::affine_element>(data, 0);
if (first_element != bb::srs::BN254_G1_FIRST_ELEMENT) {
throw_or_abort("Downloaded BN254 G1 CRS first element does not match expected point.");
}

// Verify second element if we have enough data
if (data.size() >= 2 * sizeof(bb::g1::affine_element)) {
auto second_element = from_buffer<bb::g1::affine_element>(data, sizeof(bb::g1::affine_element));
if (second_element != bb::srs::get_bn254_g1_second_element()) {
throw_or_abort("Downloaded BN254 G1 CRS second element does not match expected point.");
}
}
// Verify integrity of all complete 8MB chunks against embedded SHA256 hashes.
// This protects against man-in-the-middle attacks on HTTP downloads without requiring SSL/TLS.
bb::srs::verify_bn254_crs_integrity(data);

return data;
}
Expand Down
162 changes: 162 additions & 0 deletions barretenberg/scripts/generate_crs_hashes.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
#!/usr/bin/env bash
# Generate SHA256 chunk hashes for BN254 CRS G1 data.
# Outputs a C++ header file (bn254_crs_hashes.hpp) with embedded hashes.
#
# Usage: ./generate_crs_hashes.sh [crs_file] [output_file]
# crs_file: Path to bn254_g1.dat (default: downloads from CDN)
# output_file: Output header path (default: stdout)
#
# Requirements: python3, curl (if downloading)

set -euo pipefail

CRS_FILE="${1:-}"
OUTPUT_FILE="${2:-}"
CHUNK_SIZE=8388608 # 8MB

# Download CRS if not provided
if [ -z "$CRS_FILE" ]; then
CRS_FILE=$(mktemp /tmp/bn254_g1_XXXXXX.dat)
trap 'rm -f "$CRS_FILE"' EXIT

# Download 2^25 + 1 points = 33,554,433 * 64 bytes
NUM_BYTES=$((33554433 * 64))
END_BYTE=$((NUM_BYTES - 1))

PRIMARY_URL="http://crs.aztec-cdn.foundation/g1.dat"
FALLBACK_URL="http://crs.aztec-labs.com/g1.dat"

echo "Downloading BN254 CRS G1 data ($NUM_BYTES bytes)..." >&2
if ! curl -f --range "0-$END_BYTE" "$PRIMARY_URL" -o "$CRS_FILE" 2>/dev/null; then
echo "Primary download failed, trying fallback..." >&2
curl -f --range "0-$END_BYTE" "$FALLBACK_URL" -o "$CRS_FILE"
fi
echo "Download complete." >&2
fi

if [ ! -f "$CRS_FILE" ]; then
echo "Error: CRS file not found: $CRS_FILE" >&2
exit 1
fi

python3 -c "
import hashlib, sys, os

CHUNK_SIZE = $CHUNK_SIZE
crs_file = '$CRS_FILE'
hashes = []

with open(crs_file, 'rb') as f:
while True:
chunk = f.read(CHUNK_SIZE)
if not chunk:
break
hashes.append(hashlib.sha256(chunk).digest())

file_size = os.path.getsize(crs_file)
full_chunks = file_size // CHUNK_SIZE
partial_size = file_size % CHUNK_SIZE
points_per_chunk = CHUNK_SIZE // 64

out = sys.stdout
out.write('#pragma once\n')
out.write('#include \"barretenberg/common/thread.hpp\"\n')
out.write('#include \"barretenberg/common/throw_or_abort.hpp\"\n')
out.write('#include \"barretenberg/crypto/sha256/sha256.hpp\"\n')
out.write('#include <array>\n')
out.write('#include <atomic>\n')
out.write('#include <cstddef>\n')
out.write('#include <cstdint>\n')
out.write('#include <span>\n')
out.write('#include <string>\n')
out.write('#include <vector>\n')
out.write('\n')
out.write('namespace bb::srs {\n')
out.write('\n')
out.write('/**\n')
out.write(' * @brief SHA256 hashes for integrity verification of downloaded BN254 CRS G1 data.\n')
out.write(' *\n')
out.write(' * @details The CRS file is divided into 8MB (8,388,608 byte) chunks. Each entry contains\n')
out.write(' * the SHA256 hash of the corresponding chunk. Downloads are rounded up to 8MB boundaries\n')
out.write(' * so that every downloaded chunk can be fully verified.\n')
out.write(' *\n')
out.write(f' * Source file: bn254_g1.dat ({file_size} bytes, {file_size // 64} G1 points)\n')
out.write(f' * Chunk size: {CHUNK_SIZE} bytes ({points_per_chunk} points per chunk)\n')
out.write(f' * Total chunks: {len(hashes)} ({full_chunks} full + 1 partial of {partial_size} bytes)\n')
out.write(' *\n')
out.write(' * Regenerate with: barretenberg/scripts/generate_crs_hashes.sh\n')
out.write(' */\n')
out.write(f'constexpr size_t CRS_HASH_CHUNK_SIZE = {CHUNK_SIZE};\n')
out.write(f'constexpr size_t CRS_NUM_CHUNK_HASHES = {len(hashes)};\n')
out.write(f'// Number of full {CHUNK_SIZE // (1024*1024)}MB chunks in the CRS (the last chunk is only {partial_size} bytes).\n')
out.write(f'constexpr size_t CRS_NUM_FULL_CHUNKS = {full_chunks};\n')
out.write('\n')
out.write('// clang-format off\n')
out.write(f'inline const std::array<crypto::Sha256Hash, {len(hashes)}> BN254_CRS_CHUNK_HASHES = {{{{\n')
for i, h in enumerate(hashes):
hex_bytes = ', '.join(f'0x{b:02x}' for b in h)
comma = ',' if i < len(hashes) - 1 else ''
out.write(f' {{ {hex_bytes} }}{comma}\n')
out.write('}};\n')
out.write('// clang-format on\n')
out.write('\n')
out.write('/**\n')
out.write(' * @brief Verify downloaded CRS data against embedded SHA256 chunk hashes.\n')
out.write(' *\n')
out.write(' * @details Verifies the integrity of downloaded CRS data by checking SHA256 hashes\n')
out.write(' * of each 8MB chunk in parallel across available cores. The data must be aligned to\n')
out.write(' * the 8MB chunk size. Any trailing bytes smaller than a chunk are skipped.\n')
out.write(' * This provides integrity verification for CRS data downloaded over HTTP without\n')
out.write(' * requiring SSL/TLS.\n')
out.write(' *\n')
out.write(' * @param data The downloaded CRS data bytes (must be a multiple of CRS_HASH_CHUNK_SIZE)\n')
out.write(' * @throws If the data is not chunk-aligned or any chunk hash does not match\n')
out.write(' */\n')
out.write('inline void verify_bn254_crs_integrity(const std::vector<uint8_t>& data)\n')
out.write('{\n')
out.write(' // Verify all complete 8MB chunks. Any trailing bytes smaller than a chunk are\n')
out.write(' // not covered by chunk hashes (the full CRS has a 64-byte remainder after 256\n')
out.write(' // full chunks) and are skipped.\n')
out.write(' size_t num_full_chunks = data.size() / CRS_HASH_CHUNK_SIZE;\n')
out.write(' size_t chunks_to_verify = std::min(num_full_chunks, CRS_NUM_CHUNK_HASHES);\n')
out.write(' if (chunks_to_verify == 0) {\n')
out.write(' return;\n')
out.write(' }\n')
out.write('\n')
out.write(' // Track the first failing chunk index across threads.\n')
out.write(' std::atomic<size_t> failed_chunk{ chunks_to_verify }; // sentinel = no failure\n')
out.write('\n')
out.write(' parallel_for([&](const ThreadChunk& tc) {\n')
out.write(' for (size_t i : tc.range(chunks_to_verify)) {\n')
out.write(' // Early exit if another thread already found a mismatch.\n')
out.write(' if (failed_chunk.load(std::memory_order_relaxed) < chunks_to_verify) {\n')
out.write(' return;\n')
out.write(' }\n')
out.write(' size_t offset = i * CRS_HASH_CHUNK_SIZE;\n')
out.write(' auto chunk = std::span<const uint8_t>(data.data() + offset, CRS_HASH_CHUNK_SIZE);\n')
out.write(' auto hash = crypto::sha256(chunk);\n')
out.write(' if (hash != BN254_CRS_CHUNK_HASHES[i]) {\n')
out.write(' // Record this failure (only the first CAS wins).\n')
out.write(' size_t expected = chunks_to_verify;\n')
out.write(' failed_chunk.compare_exchange_strong(expected, i, std::memory_order_relaxed);\n')
out.write(' }\n')
out.write(' }\n')
out.write(' });\n')
out.write('\n')
out.write(' size_t bad = failed_chunk.load();\n')
out.write(' if (bad < chunks_to_verify) {\n')
out.write(' size_t offset = bad * CRS_HASH_CHUNK_SIZE;\n')
out.write(' throw_or_abort(\"CRS integrity check failed: SHA256 mismatch at chunk \" + std::to_string(bad) +\n')
out.write(' \" (bytes \" + std::to_string(offset) + \"-\" +\n')
out.write(' std::to_string(offset + CRS_HASH_CHUNK_SIZE - 1) + \")\");\n')
out.write(' }\n')
out.write('}\n')
out.write('\n')
out.write('} // namespace bb::srs\n')

print(f'Generated {len(hashes)} chunk hashes for {file_size} bytes', file=sys.stderr)
" ${OUTPUT_FILE:+> "$OUTPUT_FILE"}

if [ -n "$OUTPUT_FILE" ]; then
echo "Written to: $OUTPUT_FILE" >&2
fi
Loading