diff --git a/barretenberg/README.md b/barretenberg/README.md index d04a2909d32b..fb1f4571c09b 100644 --- a/barretenberg/README.md +++ b/barretenberg/README.md @@ -10,7 +10,7 @@ Barretenberg (or `bb` for short) is an optimized elliptic curve library for the - [Barretenberg](#barretenberg) - [Development](#development) - - [Bootstrap](#bootstrap) + - [Quick Start](#quick-start) - [Build Options and Instructions](#build-options-and-instructions) - [WASM build](#wasm-build) - [Fuzzing build](#fuzzing-build) @@ -19,7 +19,6 @@ Barretenberg (or `bb` for short) is an optimized elliptic curve library for the - [Testing](#testing) - [Integration tests with Aztec in Monorepo](#integration-tests-with-aztec-in-monorepo) - [Integration tests with Aztec in Barretenberg Standalone Repo](#integration-tests-with-aztec-in-barretenberg-standalone-repo) - - [Testing locally in docker](#testing-locally-in-docker) - [Docs Build](#docs-build) - [Benchmarks](#benchmarks) - [x86\_64](#x86_64) @@ -92,15 +91,33 @@ git clone -b release/10.x --depth 1 https://github.com/llvm/llvm-project.git \ -### Bootstrap +### Quick Start -The bootstrap script will build both the native and wasm versions of barretenberg: +If you just want to use the `bb` binary and don't need to modify the source code, the easiest option is to install a pre-built binary using [bbup](bbup/README.md): ```bash -cd cpp -./bootstrap.sh +curl -L https://raw.githubusercontent.com/AztecProtocol/aztec-packages/refs/heads/master/barretenberg/bbup/install | bash +bbup +bb --version ``` +To build from source, use the bootstrap script. By default it uses a [Zig](https://ziglang.org/)-wrapped Clang for portable binaries (glibc 2.35+). This requires `zig` to be installed in addition to the dependencies listed above: + +```bash +cd barretenberg/cpp +./bootstrap.sh # Full build (native + WASM) +./bootstrap.sh build_native # Native only (faster) +``` + +If you don't have Zig installed, you can use the `default` preset which only requires a standard Clang toolchain: + +```bash +cd barretenberg/cpp +NATIVE_PRESET=default ./bootstrap.sh build_native +``` + +The resulting binary will be at `build/bin/bb`. + ### Build Options and Instructions CMake can be passed various build options on its command line: @@ -237,19 +254,6 @@ CI will automatically run integration tests against Aztec. It is located in the When working on a PR, you may want to point this file to a different Aztec branch or commit, but then it should probably be pointed back to master before merging. -##### Testing locally in docker - -A common issue that arises is that our CI system has a different compiler version e.g. namely for GCC. If you need to mimic the CI operating system locally you can use bootstrap_docker.sh or run dockerfiles directly. However, there is a more efficient workflow for iterative development: - -``` -cd barretenberg/cpp -./scripts/docker_interactive.sh -mv build build-native # your native build folders are mounted, but will not work! have to clear them -cmake --preset gcc ; cmake --build build -``` - -This will allow you to rebuild as efficiently as if you were running native code, and not have to see a full compile cycle. - ### Docs Build If doxygen is installed on the system, you can use the **build_docs** target to build documentation, which can be configured in vscode CMake extension or using diff --git a/barretenberg/cpp/.clangd b/barretenberg/cpp/.clangd index 4210cd329af0..3711bbe5038b 100644 --- a/barretenberg/cpp/.clangd +++ b/barretenberg/cpp/.clangd @@ -2,6 +2,9 @@ CompileFlags: # Tweak the parse settings Remove: - "-fconstexpr-ops-limit=*" - "-Wfatal-errors" +Diagnostics: + Suppress: + - drv_triple_version_invalid --- # Applies all barretenberg source files If: diff --git a/barretenberg/cpp/CLAUDE.md b/barretenberg/cpp/CLAUDE.md index c1e61bee6448..c0276f8e026a 100644 --- a/barretenberg/cpp/CLAUDE.md +++ b/barretenberg/cpp/CLAUDE.md @@ -57,6 +57,13 @@ Note: Once you enable AVM, subsequent `ninja` calls will include AVM targets unt - **dsl/** - ACIR definition in C++. This is dictated by the serialization in noir/, so refactor should generally not change the structure without confirming that the user is changing noir. - **vm2/** - AVM implementation (not enabled, but might need to be fixed for compilation issues in root ./bootstrap.sh). If working in vm2, use barretenberg/cpp/src/barretenberg/vm2/CLAUDE.md +## Code formatting + +All C++ files must be formatted with clang-format before committing: +```bash +clang-format-20 -i +``` + ## Benchmarking: **IMPORTANT**: In the barretenberg context, "bench" or "benchmark" almost always means running `benchmark_remote.sh` for the given target on a remote benchmarking machine. diff --git a/barretenberg/cpp/CMakePresets.json b/barretenberg/cpp/CMakePresets.json index b38e201cdcd1..94259775c1fd 100644 --- a/barretenberg/cpp/CMakePresets.json +++ b/barretenberg/cpp/CMakePresets.json @@ -41,7 +41,9 @@ "binaryDir": "build", "environment": { "CC": "${sourceDir}/scripts/zig-cc.sh", - "CXX": "${sourceDir}/scripts/zig-c++.sh" + "CXX": "${sourceDir}/scripts/zig-c++.sh", + "CFLAGS": "-g0", + "CXXFLAGS": "-g0" }, "cacheVariables": { "CMAKE_AR": "${sourceDir}/scripts/zig-ar.sh", @@ -464,7 +466,9 @@ "binaryDir": "${sourceDir}/build-${presetName}", "environment": { "CC": "zig cc", - "CXX": "zig c++" + "CXX": "zig c++", + "CFLAGS": "-g0", + "CXXFLAGS": "-g0" }, "cacheVariables": { "ENABLE_PIC": "ON", diff --git a/barretenberg/cpp/cmake/lmdb.cmake b/barretenberg/cpp/cmake/lmdb.cmake index 1a25e8f5c90c..733ba27cca6e 100644 --- a/barretenberg/cpp/cmake/lmdb.cmake +++ b/barretenberg/cpp/cmake/lmdb.cmake @@ -15,7 +15,7 @@ ExternalProject_Add( SOURCE_DIR ${LMDB_PREFIX}/src/lmdb_repo BUILD_IN_SOURCE YES CONFIGURE_COMMAND "" # No configure step - BUILD_COMMAND ${CMAKE_COMMAND} -E env CC=${CMAKE_C_COMPILER}${CMAKE_C_COMPILER_ARG1} AR=${CMAKE_AR} make -e -C libraries/liblmdb XCFLAGS=-fPIC liblmdb.a + BUILD_COMMAND ${CMAKE_COMMAND} -E env --unset=CFLAGS --unset=CXXFLAGS CC=${CMAKE_C_COMPILER}${CMAKE_C_COMPILER_ARG1} AR=${CMAKE_AR} make -e -C libraries/liblmdb XCFLAGS=-fPIC liblmdb.a INSTALL_COMMAND "" UPDATE_COMMAND "" # No update step BUILD_BYPRODUCTS ${LMDB_LIB} diff --git a/barretenberg/cpp/scripts/audit/audit_scopes/polynomial_audit_scope.md b/barretenberg/cpp/scripts/audit/audit_scopes/polynomial_audit_scope.md index 35cd9169bb4a..bfcf864888ee 100644 --- a/barretenberg/cpp/scripts/audit/audit_scopes/polynomial_audit_scope.md +++ b/barretenberg/cpp/scripts/audit/audit_scopes/polynomial_audit_scope.md @@ -12,13 +12,12 @@ Note: Paths relative to `aztec-packages/barretenberg/cpp/src/barretenberg` 4. `polynomials/eq_polynomial.hpp` 5. `polynomials/evaluation_domain.cpp` 6. `polynomials/evaluation_domain.hpp` -7. `polynomials/iterate_over_domain.hpp` -8. `polynomials/polynomial.cpp` -9. `polynomials/polynomial.hpp` -10. `polynomials/polynomial_arithmetic.cpp` -11. `polynomials/polynomial_arithmetic.hpp` -12. `polynomials/shared_shifted_virtual_zeroes_array.hpp` -13. `polynomials/univariate_coefficient_basis.hpp` +7. `polynomials/polynomial.cpp` +8. `polynomials/polynomial.hpp` +9. `polynomials/polynomial_arithmetic.cpp` +10. `polynomials/polynomial_arithmetic.hpp` +11. `polynomials/shared_shifted_virtual_zeroes_array.hpp` +12. `polynomials/univariate_coefficient_basis.hpp` ## Summary of Module diff --git a/barretenberg/cpp/scripts/docker_interactive.sh b/barretenberg/cpp/scripts/docker_interactive.sh deleted file mode 100755 index 65349d14b7b9..000000000000 --- a/barretenberg/cpp/scripts/docker_interactive.sh +++ /dev/null @@ -1,16 +0,0 @@ -# Script to enter a docker shell. -# This comes in two flavors, ubuntu and alpine. -# This mounts the current folder into the image, allowing for in-docker development. -# You then need to run cmake commands fresh to build. -# Ideally you can combine with a fresh clone of the repo to keep it persistently around, or move around build dirs as needed. -# This is useful for debugging issues like bb being slow on alpine or running into errors on a specific version of gcc. -# usage: ./docker_interactive.sh ubuntu or ./docker_interactive.sh alpine - -# Enter script directory. -set -eu -cd $(dirname $0) -# allows for 'alpine', 'ubuntu' or 'msan.ubuntu' -ENVIRONMENT_KIND="${1:-alpine}" -DOCKERFILE="../dockerfiles/interactive/Dockerfile.$ENVIRONMENT_KIND" -docker build -t "env-$ENVIRONMENT_KIND" -f "$DOCKERFILE" . -docker run -it --mount type=bind,source=$(pwd)/..,target=/usr/src/barretenberg/cpp "env-$ENVIRONMENT_KIND" diff --git a/barretenberg/cpp/scripts/test_chonk_standalone_vks_havent_changed.sh b/barretenberg/cpp/scripts/test_chonk_standalone_vks_havent_changed.sh index 290839e6cfcf..b627fc0d0aac 100755 --- a/barretenberg/cpp/scripts/test_chonk_standalone_vks_havent_changed.sh +++ b/barretenberg/cpp/scripts/test_chonk_standalone_vks_havent_changed.sh @@ -150,8 +150,12 @@ elif [[ "${1:-}" == "--update_inputs" ]]; then # Generate new inputs echo "Running bootstrap to generate new IVC inputs..." - BOOTSTRAP_TO=yarn-project ../../bootstrap.sh # bootstrap aztec-packages from root - ../../yarn-project/end-to-end/bootstrap.sh build_bench # build bench to generate IVC inputs + cd "$root" + ./bootstrap.sh pull_submodules + make yarn-project + cd yarn-project/end-to-end + ./bootstrap.sh build_bench # build bench to generate IVC inputs + cd "$root/barretenberg/cpp/scripts" compress_and_upload "$inputs_dir" diff --git a/barretenberg/cpp/src/barretenberg/api/api_chonk.cpp b/barretenberg/cpp/src/barretenberg/api/api_chonk.cpp index c7ef0746710b..20347f652292 100644 --- a/barretenberg/cpp/src/barretenberg/api/api_chonk.cpp +++ b/barretenberg/cpp/src/barretenberg/api/api_chonk.cpp @@ -115,6 +115,36 @@ bool ChonkAPI::verify([[maybe_unused]] const Flags& flags, return response.valid; } +bool ChonkAPI::batch_verify([[maybe_unused]] const Flags& flags, const std::filesystem::path& proofs_dir) +{ + BB_BENCH_NAME("ChonkAPI::batch_verify"); + + std::vector proofs; + std::vector> vks; + + for (size_t i = 0;; ++i) { + auto proof_file = proofs_dir / ("proof_" + std::to_string(i)); + auto vk_file = proofs_dir / ("vk_" + std::to_string(i)); + + if (!std::filesystem::exists(proof_file) || !std::filesystem::exists(vk_file)) { + break; + } + + auto proof_fields = many_from_buffer(read_file(proof_file)); + proofs.push_back(ChonkProof::from_field_elements(proof_fields)); + vks.push_back(read_vk_file(vk_file)); + } + + if (proofs.empty()) { + throw_or_abort("batch_verify: no proof_0/vk_0 pairs found in " + proofs_dir.string()); + } + + info("ChonkAPI::batch_verify - found ", proofs.size(), " proof/vk pairs in ", proofs_dir.string()); + + auto response = bbapi::ChonkBatchVerify{ .proofs = std::move(proofs), .vks = std::move(vks) }.execute(); + return response.valid; +} + // WORKTODO(bbapi) remove this bool ChonkAPI::prove_and_verify(const std::filesystem::path& input_path) { diff --git a/barretenberg/cpp/src/barretenberg/api/api_chonk.hpp b/barretenberg/cpp/src/barretenberg/api/api_chonk.hpp index 877896c38969..f14e03810253 100644 --- a/barretenberg/cpp/src/barretenberg/api/api_chonk.hpp +++ b/barretenberg/cpp/src/barretenberg/api/api_chonk.hpp @@ -81,6 +81,14 @@ class ChonkAPI : public API { const std::filesystem::path& bytecode_path, const std::filesystem::path& output_path) override; + /** + * @brief Batch-verify multiple Chonk proofs from a directory of proof_N/vk_N pairs. + * + * @param proofs_dir Directory containing proof_0/vk_0, proof_1/vk_1, ... + * @return true if all proofs verify + */ + bool batch_verify(const Flags& flags, const std::filesystem::path& proofs_dir); + /** * @brief Validate that precomputed VKs in ivc-inputs.msgpack match computed VKs. * diff --git a/barretenberg/cpp/src/barretenberg/bb/cli.cpp b/barretenberg/cpp/src/barretenberg/bb/cli.cpp index 4474232d6cf5..f3bd856c9fcd 100644 --- a/barretenberg/cpp/src/barretenberg/bb/cli.cpp +++ b/barretenberg/cpp/src/barretenberg/bb/cli.cpp @@ -519,6 +519,20 @@ int parse_and_run_cli_command(int argc, char* argv[]) remove_zk_option(verify); add_ipa_accumulation_flag(verify); + /*************************************************************************************************************** + * Subcommand: batch_verify + ***************************************************************************************************************/ + std::filesystem::path batch_verify_proofs_dir{ "./proofs" }; + CLI::App* batch_verify = + app.add_subcommand("batch_verify", "Batch-verify multiple Chonk proofs with a single IPA SRS MSM."); + + add_help_extended_flag(batch_verify); + add_scheme_option(batch_verify); + batch_verify->add_option("--proofs_dir", batch_verify_proofs_dir, "Directory containing proof_N/vk_N pairs."); + add_verbose_flag(batch_verify); + add_debug_flag(batch_verify); + add_crs_path_option(batch_verify); + /*************************************************************************************************************** * Subcommand: write_solidity_verifier ***************************************************************************************************************/ @@ -954,6 +968,11 @@ int parse_and_run_cli_command(int argc, char* argv[]) } return api.check_precomputed_vks(flags, ivc_inputs_path) ? 0 : 1; } + if (batch_verify->parsed()) { + const bool verified = api.batch_verify(flags, batch_verify_proofs_dir); + vinfo("batch verified: ", verified); + return verified ? 0 : 1; + } return execute_non_prove_command(api); } else if (flags.scheme == "ultra_honk") { UltraHonkAPI api; diff --git a/barretenberg/cpp/src/barretenberg/bbapi/bbapi.test.cpp b/barretenberg/cpp/src/barretenberg/bbapi/bbapi.test.cpp index 4ccd0199f02b..9c096e7c10a2 100644 --- a/barretenberg/cpp/src/barretenberg/bbapi/bbapi.test.cpp +++ b/barretenberg/cpp/src/barretenberg/bbapi/bbapi.test.cpp @@ -22,7 +22,8 @@ using Commands = ::testing::Types; + bbapi::ChonkCheckPrecomputedVk, + bbapi::ChonkBatchVerify>; // Typed test suites template class BBApiMsgpack : public ::testing::Test {}; diff --git a/barretenberg/cpp/src/barretenberg/bbapi/bbapi_chonk.cpp b/barretenberg/cpp/src/barretenberg/bbapi/bbapi_chonk.cpp index 0fb97f41e8cb..7be5bd4be250 100644 --- a/barretenberg/cpp/src/barretenberg/bbapi/bbapi_chonk.cpp +++ b/barretenberg/cpp/src/barretenberg/bbapi/bbapi_chonk.cpp @@ -1,4 +1,5 @@ #include "barretenberg/bbapi/bbapi_chonk.hpp" +#include "barretenberg/chonk/chonk_batch_verifier.hpp" #include "barretenberg/chonk/chonk_verifier.hpp" #include "barretenberg/chonk/mock_circuit_producer.hpp" #include "barretenberg/chonk/proof_compression.hpp" @@ -157,6 +158,43 @@ ChonkVerify::Response ChonkVerify::execute(const BBApiRequest& /*request*/) && return { .valid = verified }; } +ChonkBatchVerify::Response ChonkBatchVerify::execute(const BBApiRequest& /*request*/) && +{ + BB_BENCH_NAME(MSGPACK_SCHEMA_NAME); + + if (proofs.size() != vks.size()) { + throw_or_abort("ChonkBatchVerify: proofs.size() (" + std::to_string(proofs.size()) + ") != vks.size() (" + + std::to_string(vks.size()) + ")"); + } + if (proofs.empty()) { + throw_or_abort("ChonkBatchVerify: no proofs provided"); + } + + using VerificationKey = Chonk::MegaVerificationKey; + + std::vector inputs; + inputs.reserve(proofs.size()); + + for (size_t i = 0; i < proofs.size(); ++i) { + validate_vk_size(vks[i]); + auto hiding_kernel_vk = std::make_shared(from_buffer(vks[i])); + + const size_t expected_proof_size = + static_cast(hiding_kernel_vk->num_public_inputs) + ChonkProof::PROOF_LENGTH_WITHOUT_PUB_INPUTS; + if (proofs[i].size() != expected_proof_size) { + throw_or_abort("ChonkBatchVerify: proof[" + std::to_string(i) + "] has wrong size: expected " + + std::to_string(expected_proof_size) + ", got " + std::to_string(proofs[i].size())); + } + + auto vk_and_hash = std::make_shared(hiding_kernel_vk); + inputs.push_back({ .proof = std::move(proofs[i]), .vk_and_hash = std::move(vk_and_hash) }); + } + + const bool verified = ChonkBatchVerifier::verify(inputs); + + return { .valid = verified }; +} + static std::shared_ptr get_acir_program_prover_instance(acir_format::AcirProgram& program) { Chonk::ClientCircuit builder = acir_format::create_circuit(program); diff --git a/barretenberg/cpp/src/barretenberg/bbapi/bbapi_chonk.hpp b/barretenberg/cpp/src/barretenberg/bbapi/bbapi_chonk.hpp index d156a5a0c6c3..293a3282685a 100644 --- a/barretenberg/cpp/src/barretenberg/bbapi/bbapi_chonk.hpp +++ b/barretenberg/cpp/src/barretenberg/bbapi/bbapi_chonk.hpp @@ -239,6 +239,27 @@ struct ChonkStats { bool operator==(const ChonkStats&) const = default; }; +/** + * @struct ChonkBatchVerify + * @brief Batch-verify multiple Chonk proofs with a single IPA SRS MSM + */ +struct ChonkBatchVerify { + static constexpr const char MSGPACK_SCHEMA_NAME[] = "ChonkBatchVerify"; + + struct Response { + static constexpr const char MSGPACK_SCHEMA_NAME[] = "ChonkBatchVerifyResponse"; + bool valid; + MSGPACK_FIELDS(valid); + bool operator==(const Response&) const = default; + }; + + std::vector proofs; + std::vector> vks; + Response execute(const BBApiRequest& request = {}) &&; + MSGPACK_FIELDS(proofs, vks); + bool operator==(const ChonkBatchVerify&) const = default; +}; + /** * @struct ChonkCompressProof * @brief Compress a Chonk proof to a compact byte representation diff --git a/barretenberg/cpp/src/barretenberg/bbapi/bbapi_execute.hpp b/barretenberg/cpp/src/barretenberg/bbapi/bbapi_execute.hpp index ab16da99a508..7c2db20e993d 100644 --- a/barretenberg/cpp/src/barretenberg/bbapi/bbapi_execute.hpp +++ b/barretenberg/cpp/src/barretenberg/bbapi/bbapi_execute.hpp @@ -23,6 +23,7 @@ using Command = NamedUnion(message.data()), message.size()); auto sig = crypto::schnorr_construct_signature(message_str, key_pair); + crypto::secure_erase_bytes(&key_pair.private_key, sizeof(key_pair.private_key)); return { sig.s, sig.e }; } diff --git a/barretenberg/cpp/src/barretenberg/benchmark/chonk_bench/chonk.bench.cpp b/barretenberg/cpp/src/barretenberg/benchmark/chonk_bench/chonk.bench.cpp index 8792d36211f9..ecbd1c84a8e2 100644 --- a/barretenberg/cpp/src/barretenberg/benchmark/chonk_bench/chonk.bench.cpp +++ b/barretenberg/cpp/src/barretenberg/benchmark/chonk_bench/chonk.bench.cpp @@ -6,6 +6,7 @@ #include #include +#include "barretenberg/chonk/chonk_batch_verifier.hpp" #include "barretenberg/chonk/chonk_verifier.hpp" #include "barretenberg/chonk/proof_compression.hpp" #include "barretenberg/chonk/test_bench_shared.hpp" @@ -40,6 +41,7 @@ BENCHMARK_DEFINE_F(ChonkBench, VerificationOnly)(benchmark::State& state) auto [proof, vk_and_hash] = accumulate_and_prove_with_precomputed_vks(NUM_APP_CIRCUITS, precomputed_vks); for (auto _ : state) { + GOOGLE_BB_BENCH_REPORTER(state); ChonkNativeVerifier verifier(vk_and_hash); benchmark::DoNotOptimize(verifier.verify(proof)); } @@ -90,12 +92,53 @@ BENCHMARK_DEFINE_F(ChonkBench, ProofDecompress)(benchmark::State& state) } } +/** + * @brief Benchmark N individual Chonk verifications (sequential). Baseline for batch comparison. + */ +BENCHMARK_DEFINE_F(ChonkBench, VerifyIndividual)(benchmark::State& state) +{ + const size_t num_proofs = static_cast(state.range(0)); + auto precomputed_vks = precompute_vks(1); + + // Generate a single proof and reuse it N times + auto [proof, vk_and_hash] = accumulate_and_prove_with_precomputed_vks(1, precomputed_vks); + + for (auto _ : state) { + for (size_t i = 0; i < num_proofs; i++) { + ChonkNativeVerifier verifier(vk_and_hash); + benchmark::DoNotOptimize(verifier.verify(proof)); + } + } +} + +/** + * @brief Benchmark batch verification of N Chonk proofs (single SRS MSM). + */ +BENCHMARK_DEFINE_F(ChonkBench, BatchVerify)(benchmark::State& state) +{ + const size_t num_proofs = static_cast(state.range(0)); + auto precomputed_vks = precompute_vks(1); + + // Generate a single proof and reuse it N times + auto [proof, vk_and_hash] = accumulate_and_prove_with_precomputed_vks(1, precomputed_vks); + std::vector inputs(num_proofs); + for (size_t i = 0; i < num_proofs; i++) { + inputs[i] = { proof, vk_and_hash }; + } + + for (auto _ : state) { + benchmark::DoNotOptimize(ChonkBatchVerifier::verify(inputs)); + } +} + #define ARGS Arg(ChonkBench::NUM_ITERATIONS_MEDIUM_COMPLEXITY)->Arg(2) BENCHMARK_REGISTER_F(ChonkBench, Full)->Unit(benchmark::kMillisecond)->ARGS; BENCHMARK_REGISTER_F(ChonkBench, VerificationOnly)->Unit(benchmark::kMillisecond); BENCHMARK_REGISTER_F(ChonkBench, ProofCompress)->Unit(benchmark::kMillisecond); BENCHMARK_REGISTER_F(ChonkBench, ProofDecompress)->Unit(benchmark::kMillisecond); +BENCHMARK_REGISTER_F(ChonkBench, VerifyIndividual)->Unit(benchmark::kMillisecond)->Arg(1)->Arg(2)->Arg(4)->Arg(8); +BENCHMARK_REGISTER_F(ChonkBench, BatchVerify)->Unit(benchmark::kMillisecond)->Arg(1)->Arg(2)->Arg(4)->Arg(8); } // namespace diff --git a/barretenberg/cpp/src/barretenberg/benchmark/ipa_bench/ipa.bench.cpp b/barretenberg/cpp/src/barretenberg/benchmark/ipa_bench/ipa.bench.cpp index 67242d17cf0a..8701df7313c1 100644 --- a/barretenberg/cpp/src/barretenberg/benchmark/ipa_bench/ipa.bench.cpp +++ b/barretenberg/cpp/src/barretenberg/benchmark/ipa_bench/ipa.bench.cpp @@ -66,6 +66,83 @@ void ipa_verify(State& state) noexcept BB_ASSERT(result); } } + +// Batch verification benchmarks: compare N individual verifications vs one batch verification. +// Uses default IPA poly_length (ECCVM size = 2^17) to match real Chonk usage. + +// Pre-generated single proof for batch benchmarks (reused N times to avoid expensive setup) +OpeningClaim batch_claim; +HonkProof batch_proof_data_single; + +static void DoBatchSetup(const benchmark::State&) +{ + srs::init_file_crs_factory(srs::bb_crs_path()); + static constexpr size_t BENCH_POLY_LENGTH = IPA::poly_length; + static bool initialized = false; + if (initialized) { + return; + } + initialized = true; + + ck = CommitmentKey(BENCH_POLY_LENGTH); + vk = VerifierCommitmentKey(BENCH_POLY_LENGTH, srs::get_grumpkin_crs_factory()); + + numeric::RNG& engine = numeric::get_debug_randomness(); + Polynomial poly(BENCH_POLY_LENGTH); + for (size_t j = 0; j < BENCH_POLY_LENGTH; j++) { + poly.at(j) = Fr::random_element(&engine); + } + auto x = Fr::random_element(&engine); + auto eval = poly.evaluate(x); + batch_claim = { { x, eval }, ck.commit(poly) }; + + auto pt = std::make_shared(); + IPA::compute_opening_proof(ck, { poly, { x, eval } }, pt); + batch_proof_data_single = pt->export_proof(); +} + +/** + * @brief Verify N IPA proofs individually (sequential). Baseline for comparison. + */ +void ipa_verify_individual(State& state) noexcept +{ + const size_t num_proofs = static_cast(state.range(0)); + for (auto _ : state) { + state.PauseTiming(); + // Create fresh verifier transcripts from the same proof data + std::vector> transcripts(num_proofs); + for (size_t i = 0; i < num_proofs; i++) { + transcripts[i] = std::make_shared(batch_proof_data_single); + } + state.ResumeTiming(); + + for (size_t i = 0; i < num_proofs; i++) { + auto result = IPA::reduce_verify(vk, batch_claim, transcripts[i]); + BB_ASSERT(result); + } + } +} + +/** + * @brief Verify N IPA proofs with batched SRS MSM. + */ +void ipa_batch_verify(State& state) noexcept +{ + const size_t num_proofs = static_cast(state.range(0)); + for (auto _ : state) { + state.PauseTiming(); + std::vector> claims(num_proofs, batch_claim); + std::vector> transcripts(num_proofs); + for (size_t i = 0; i < num_proofs; i++) { + transcripts[i] = std::make_shared(batch_proof_data_single); + } + state.ResumeTiming(); + + auto result = IPA::batch_reduce_verify(vk, claims, transcripts); + BB_ASSERT(result); + } +} + } // namespace BENCHMARK(ipa_open) ->Unit(kMillisecond) @@ -75,4 +152,6 @@ BENCHMARK(ipa_verify) ->Unit(kMillisecond) ->DenseRange(MIN_POLYNOMIAL_DEGREE_LOG2, MAX_POLYNOMIAL_DEGREE_LOG2) ->Setup(DoSetup); +BENCHMARK(ipa_verify_individual)->Unit(kMillisecond)->Arg(1)->Arg(2)->Arg(4)->Arg(8)->Setup(DoBatchSetup); +BENCHMARK(ipa_batch_verify)->Unit(kMillisecond)->Arg(1)->Arg(2)->Arg(4)->Arg(8)->Setup(DoBatchSetup); BENCHMARK_MAIN(); diff --git a/barretenberg/cpp/src/barretenberg/chonk/chonk_batch_verifier.cpp b/barretenberg/cpp/src/barretenberg/chonk/chonk_batch_verifier.cpp new file mode 100644 index 000000000000..6b39bb70a90c --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/chonk/chonk_batch_verifier.cpp @@ -0,0 +1,38 @@ +#include "chonk_batch_verifier.hpp" +#include "barretenberg/commitment_schemes/ipa/ipa.hpp" +#include "barretenberg/commitment_schemes/verification_key.hpp" +#include "barretenberg/eccvm/eccvm_flavor.hpp" + +namespace bb { + +bool ChonkBatchVerifier::verify(std::span inputs) +{ + const size_t num_proofs = inputs.size(); + if (num_proofs == 0) { + return true; + } + + // Phase 1: Run all non-IPA verification for each proof, collecting IPA claims + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1651): Consider batching and/or multithreading the + // non-IPA portion of verification as well. Becomes significant for moderate batch sizes. + std::vector> ipa_claims; + std::vector> ipa_transcripts; + ipa_claims.reserve(num_proofs); + ipa_transcripts.reserve(num_proofs); + + for (const auto& input : inputs) { + ChonkNativeVerifier verifier(input.vk_and_hash); + auto result = verifier.reduce_to_ipa_claim(input.proof); + if (!result.all_checks_passed) { + return false; + } + ipa_claims.push_back(result.ipa_claim); + ipa_transcripts.push_back(std::make_shared(result.ipa_proof)); + } + + // Phase 2: Batch IPA verification with single SRS MSM + auto ipa_vk = VerifierCommitmentKey{ ECCVMFlavor::ECCVM_FIXED_SIZE }; + return IPA::batch_reduce_verify(ipa_vk, ipa_claims, ipa_transcripts); +} + +} // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/chonk/chonk_batch_verifier.hpp b/barretenberg/cpp/src/barretenberg/chonk/chonk_batch_verifier.hpp new file mode 100644 index 000000000000..bc11ea979ae5 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/chonk/chonk_batch_verifier.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include "barretenberg/chonk/chonk_proof.hpp" +#include "barretenberg/chonk/chonk_verifier.hpp" + +namespace bb { + +/** + * @brief Batch verifier for multiple Chonk IVC proofs. + * @details Runs all non-IPA verification (MegaZK, databus, Goblin) for each proof independently, + * then batches the resulting IPA opening claims into a single IPA verification via random linear combination. + * This replaces N separate large SRS MSMs with one, giving ~Nx speedup on the IPA bottleneck. + */ +class ChonkBatchVerifier { + public: + struct Input { + ChonkProof proof; + std::shared_ptr vk_and_hash; + }; + + /** + * @brief Verify multiple Chonk proofs with batched IPA verification. + * @details For each proof, performs all non-IPA verification (MegaZK, databus, Goblin). + * If all pass, collects IPA claims and batch-verifies them with a single SRS MSM. + * Returns true only if ALL proofs verify. On failure, does not identify which proof failed. + * + * @param inputs Span of (proof, vk_and_hash) pairs to verify + * @return true if all proofs verify + */ + static bool verify(std::span inputs); +}; + +} // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/chonk/chonk_batch_verifier.test.cpp b/barretenberg/cpp/src/barretenberg/chonk/chonk_batch_verifier.test.cpp new file mode 100644 index 000000000000..3f950f602035 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/chonk/chonk_batch_verifier.test.cpp @@ -0,0 +1,93 @@ +#include "barretenberg/chonk/chonk_batch_verifier.hpp" +#include "barretenberg/chonk/chonk.hpp" +#include "barretenberg/chonk/mock_circuit_producer.hpp" +#include "barretenberg/common/test.hpp" + +using namespace bb; + +static constexpr size_t SMALL_LOG_2_NUM_GATES = 5; + +class ChonkBatchVerifierTests : public ::testing::Test { + protected: + static void SetUpTestSuite() { bb::srs::init_file_crs_factory(bb::srs::bb_crs_path()); } + + using CircuitProducer = PrivateFunctionExecutionMockCircuitProducer; + + static std::pair> generate_chonk_proof( + size_t num_app_circuits = 1) + { + CircuitProducer circuit_producer(num_app_circuits); + const size_t num_circuits = circuit_producer.total_num_circuits; + Chonk ivc{ num_circuits }; + + TestSettings settings{ .log2_num_gates = SMALL_LOG_2_NUM_GATES }; + for (size_t j = 0; j < num_circuits; ++j) { + circuit_producer.construct_and_accumulate_next_circuit(ivc, settings); + } + return { ivc.prove(), ivc.get_hiding_kernel_vk_and_hash() }; + } +}; + +TEST_F(ChonkBatchVerifierTests, BatchVerifyTwoValidProofs) +{ + auto [proof1, vk1] = generate_chonk_proof(); + auto [proof2, vk2] = generate_chonk_proof(); + + std::vector inputs = { + { std::move(proof1), vk1 }, + { std::move(proof2), vk2 }, + }; + EXPECT_TRUE(ChonkBatchVerifier::verify(inputs)); +} + +TEST_F(ChonkBatchVerifierTests, BatchVerifySingleProof) +{ + auto [proof, vk] = generate_chonk_proof(); + + std::vector inputs = { + { std::move(proof), vk }, + }; + EXPECT_TRUE(ChonkBatchVerifier::verify(inputs)); +} + +/** + * @brief Tamper with only the IPA proof, keeping all other proof components valid. + * @details Targets the batch IPA verification path (Phase 2 of ChonkBatchVerifier). + */ +TEST_F(ChonkBatchVerifierTests, BatchVerifyTamperedIPAProof) +{ + BB_DISABLE_ASSERTS(); + + auto [proof1, vk1] = generate_chonk_proof(); + auto [proof2, vk2] = generate_chonk_proof(); + + // Corrupt a field element in the IPA proof portion of the goblin proof + ASSERT_FALSE(proof2.goblin_proof.ipa_proof.empty()); + proof2.goblin_proof.ipa_proof[0] = proof2.goblin_proof.ipa_proof[0] + bb::fr(1); + + std::vector inputs = { + { std::move(proof1), vk1 }, + { std::move(proof2), vk2 }, + }; + EXPECT_FALSE(ChonkBatchVerifier::verify(inputs)); +} + +/** + * @brief Swap goblin proofs between two valid Chonk proofs to test non-IPA verification failures. + */ +TEST_F(ChonkBatchVerifierTests, BatchVerifySwappedGoblinProofs) +{ + BB_DISABLE_ASSERTS(); + + auto [proof1, vk1] = generate_chonk_proof(); + auto [proof2, vk2] = generate_chonk_proof(); + + // Swap goblin proofs: each mega_proof is now paired with the wrong goblin proof + std::swap(proof1.goblin_proof, proof2.goblin_proof); + + std::vector inputs = { + { std::move(proof1), vk1 }, + { std::move(proof2), vk2 }, + }; + EXPECT_FALSE(ChonkBatchVerifier::verify(inputs)); +} diff --git a/barretenberg/cpp/src/barretenberg/chonk/chonk_verifier.cpp b/barretenberg/cpp/src/barretenberg/chonk/chonk_verifier.cpp index 7f5d8caf2f4b..aaee550ecfe0 100644 --- a/barretenberg/cpp/src/barretenberg/chonk/chonk_verifier.cpp +++ b/barretenberg/cpp/src/barretenberg/chonk/chonk_verifier.cpp @@ -7,20 +7,22 @@ #include "chonk_verifier.hpp" #include "barretenberg/commitment_schemes/ipa/ipa.hpp" #include "barretenberg/commitment_schemes/verification_key.hpp" +#include "barretenberg/common/bb_bench.hpp" namespace bb { /** - * @brief Verifies a Chonk IVC proof (Native specialization). + * @brief Run all Chonk verification except IPA, returning the IPA data for deferred verification. */ -template <> ChonkVerifier::Output ChonkVerifier::verify(const Proof& proof) +template <> ChonkVerifier::IPAReductionResult ChonkVerifier::reduce_to_ipa_claim(const Proof& proof) { + BB_BENCH_NAME("ChonkVerifier::reduce_to_ipa_claim"); // Step 1: Verify the Hiding kernel proof (includes pairing check) HidingKernelVerifier verifier{ vk_and_hash, transcript }; auto verifier_output = verifier.verify_proof(proof.mega_proof); if (!verifier_output.result) { info("ChonkVerifier: verification failed at MegaZK verification step"); - return false; + return { {}, {}, false }; } // Extract public inputs and kernel data @@ -34,7 +36,7 @@ template <> ChonkVerifier::Output ChonkVerifier::verify(const Proo vinfo("ChonkVerifier: databus consistency verified: ", databus_consistency_verified); if (!databus_consistency_verified) { info("Chonk Verifier: verification failed at databus consistency check"); - return false; + return { {}, {}, false }; } // Step 3: Goblin verification (merge, eccvm, translator) @@ -45,13 +47,27 @@ template <> ChonkVerifier::Output ChonkVerifier::verify(const Proo if (!goblin_output.all_checks_passed) { info("ChonkVerifier: chonk verification failed at Goblin checks (merge/eccvm/translator reduction + pairing)"); + return { {}, {}, false }; + } + + return { std::move(goblin_output.ipa_claim), std::move(goblin_output.ipa_proof), true }; +} + +/** + * @brief Verifies a Chonk IVC proof (Native specialization). + */ +template <> ChonkVerifier::Output ChonkVerifier::verify(const Proof& proof) +{ + BB_BENCH_NAME("ChonkVerifier::verify"); + auto result = reduce_to_ipa_claim(proof); + if (!result.all_checks_passed) { return false; } // Step 4: Verify IPA opening - auto ipa_transcript = std::make_shared(goblin_output.ipa_proof); + auto ipa_transcript = std::make_shared(result.ipa_proof); auto ipa_vk = VerifierCommitmentKey{ ECCVMFlavor::ECCVM_FIXED_SIZE }; - bool ipa_verified = IPA::reduce_verify(ipa_vk, goblin_output.ipa_claim, ipa_transcript); + bool ipa_verified = IPA::reduce_verify(ipa_vk, result.ipa_claim, ipa_transcript); vinfo("ChonkVerifier: Goblin IPA verified: ", ipa_verified); if (!ipa_verified) { info("ChonkVerifier: Chonk verification failed at IPA check"); @@ -113,6 +129,15 @@ template <> ChonkVerifier::Output ChonkVerifier::verify(const Proof& .all_checks_passed = mega_reduction_succeeded && goblin_output.all_checks_passed }; } +/** + * @brief Stub for recursive mode (not meaningful — reduce_to_ipa_claim is only used in native batch verification). + */ +template <> +ChonkVerifier::IPAReductionResult ChonkVerifier::reduce_to_ipa_claim([[maybe_unused]] const Proof& proof) +{ + throw_or_abort("reduce_to_ipa_claim is only available for native (non-recursive) ChonkVerifier"); +} + // Template instantiations template class ChonkVerifier; // Native verifier template class ChonkVerifier; // Recursive verifier diff --git a/barretenberg/cpp/src/barretenberg/chonk/chonk_verifier.hpp b/barretenberg/cpp/src/barretenberg/chonk/chonk_verifier.hpp index c9b86a2c1a80..9b1a2824ee00 100644 --- a/barretenberg/cpp/src/barretenberg/chonk/chonk_verifier.hpp +++ b/barretenberg/cpp/src/barretenberg/chonk/chonk_verifier.hpp @@ -104,6 +104,28 @@ template class ChonkVerifier { */ [[nodiscard("IPA claim and pairing points must be accumulated")]] Output verify(const Proof& proof); + /** + * @brief Result of reducing Chonk verification to an IPA opening claim (native mode only). + * @details Contains the IPA claim and proof from non-IPA verification (MegaZK, databus, Goblin), + * allowing batch IPA verification across multiple Chonk proofs. + */ + struct IPAReductionResult { + OpeningClaim ipa_claim; + ::bb::HonkProof ipa_proof; + bool all_checks_passed; + }; + + /** + * @brief Run Chonk verification up to but not including IPA, returning the IPA claim for deferred verification. + * @details Verifies the MegaZK proof, databus consistency, and Goblin proof (merge/eccvm/translator), + * then returns the IPA opening claim and proof without performing the final IPA MSM. + * This enables batch IPA verification across multiple Chonk proofs. + * + * @param proof The Chonk proof to partially verify + * @return IPAReductionResult containing the IPA claim/proof and whether all non-IPA checks passed + */ + IPAReductionResult reduce_to_ipa_claim(const Proof& proof); + private: // VK and hash of the hiding kernel std::shared_ptr vk_and_hash; diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.hpp index 500ba9ff4356..6709a3c3aaa5 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.hpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.hpp @@ -8,6 +8,7 @@ #include "barretenberg/commitment_schemes/claim.hpp" #include "barretenberg/commitment_schemes/verification_key.hpp" #include "barretenberg/common/assert.hpp" +#include "barretenberg/common/bb_bench.hpp" #include "barretenberg/common/container.hpp" #include "barretenberg/common/thread.hpp" #include "barretenberg/common/throw_or_abort.hpp" @@ -325,44 +326,55 @@ template class IPA } /** - * @brief Natively verify the correctness of a Proof + * @brief Per-proof data extracted from an IPA transcript. + * @details Contains all values derived from transcript processing (steps 2–7, 9 of the IPA verification + * protocol) that are needed for either single-proof or batch IPA verification. + * Does not include the MSM (step 8). + */ + struct TranscriptData { + GroupElement C_zero; ///< \f$C_0 = C' + \sum_{j=0}^{k-1}(u_j^{-1}L_j + u_jR_j)\f$ + Fr b_zero; ///< \f$b_0 = g(\beta) = \prod_{i=0}^{k-1}(1+u_{i}^{-1}x^{2^{i}})\f$ + Polynomial s_vec; ///< \f$\vec{s}=(1,u_{0}^{-1},u_{1}^{-1},u_{0}^{-1}u_{1}^{-1},..., + ///< \prod_{i=0}^{k-1}u_{i}^{-1})\f$ + Fr gen_challenge; ///< Generator challenge \f$u\f$ where \f$U = u \cdot G\f$ + Commitment G_zero_from_prover; ///< \f$G_0\f$ received from prover (not recomputed) + Fr a_zero; ///< \f$a_0\f$ received from prover + }; + + /** + * @brief Process a single IPA proof's transcript, extracting all per-proof verification data. * - * @tparam Transcript Allows to specify a transcript class. Useful for testing - * @param vk Verification_key containing srs - * @param opening_claim Contains the commitment C and opening pair \f$(\beta, f(\beta))\f$ + * @param opening_claim Contains the commitment \f$C\f$ and opening pair \f$(\beta, f(\beta))\f$ * @param transcript Transcript with elements from the prover and generated challenges * - * @return true/false depending on if the proof verifies + * @return TranscriptData containing \f$C_0, b_0, \vec{s}, u, G_0, a_0\f$ * - * @details The procedure runs as follows: + * @details Performs steps 2–7 and 9 of the IPA verification protocol (see reduce_verify_internal_native): * - *1. Receive commitment, challenge, and claimed evaluation from the prover *2. Receive the generator challenge \f$u\f$, abort if it's zero, otherwise compute \f$U=u\cdot G\f$ - *3. Compute \f$C'=C+f(\beta)\cdot U\f$. (Recall that \f$f(\beta)\f$ is the claimed evaluation.) - *4. Receive \f$L_j, R_j\f$ and compute challenges \f$u_j\f$ for \f$j \in {k-1,..,0}\f$, abort immediately on - receiving a \f$u_j=0\f$ + *3. Compute \f$C'=C+f(\beta)\cdot U\f$. (Recall that \f$f(\beta)\f$ is the claimed evaluation.) + *4. Receive \f$L_j, R_j\f$ and compute challenges \f$u_j\f$ for \f$j \in {k-1,..,0}\f$, abort on \f$u_j=0\f$ *5. Compute \f$C_0 = C' + \sum_{j=0}^{k-1}(u_j^{-1}L_j + u_jR_j)\f$ *6. Compute \f$b_0=g(\beta)=\prod_{i=0}^{k-1}(1+u_{i}^{-1}x^{2^{i}})\f$ *7. Compute vector \f$\vec{s}=(1,u_{0}^{-1},u_{1}^{-1},u_{0}^{-1}u_{1}^{-1},...,\prod_{i=0}^{k-1}u_{i}^{-1})\f$ - *8. Compute \f$G_s=\langle \vec{s},\vec{G}\rangle\f$ - *9. Receive \f$\vec{a}_{0}\f$ of length 1 - *10. Compute \f$C_{right}=a_{0}G_{s}+a_{0}b_{0}U\f$ - *11. Check that \f$C_{right} = C_0\f$. If they match, return true. Otherwise return false. + * + * Additionally receives \f$G_0\f$ and \f$a_0\f$ from the prover transcript (step 9). + * Does NOT compute \f$G_s=\langle \vec{s},\vec{G}\rangle\f$ (step 8, the MSM) + * or perform the final verification check (steps 10–11). + * + * @pre add_claim_to_hash_buffer must have been called on the transcript (step 1). */ - static bool reduce_verify_internal_native(const VK& vk, const OpeningClaim& opening_claim, auto& transcript) + template + static TranscriptData read_transcript_data(const OpeningClaim& opening_claim, + const std::shared_ptr& transcript) requires(!Curve::is_stdlib_type) { - // Step 1 - // Done by `add_claim_to_hash_buffer`. - // Step 2. // Receive generator challenge u and compute auxiliary generator const Fr generator_challenge = transcript->template get_challenge("IPA:generator_challenge"); - if (generator_challenge.is_zero()) { throw_or_abort("The generator challenge can't be zero"); } - const Commitment aux_generator = Commitment::one() * generator_challenge; // Step 3. @@ -371,13 +383,11 @@ template class IPA const auto pippenger_size = 2 * log_poly_length; std::vector round_challenges(log_poly_length); - // the group elements that will participate in our MSM. - std::vector msm_elements(pippenger_size); // L_{k-1}, R_{k-1}, L_{k-2}, ..., L_0, R_0. - // the scalars that will participate in our MSM. - std::vector msm_scalars(pippenger_size); // w_{k-1}^{-1}, w_{k-1}, ..., w_{0}^{-1}, w_{0}. + std::vector msm_elements(pippenger_size); + std::vector msm_scalars(pippenger_size); // Step 4. - // Receive all L_i and R_i and populate msm_elements. + // Receive all L_j, R_j and compute round challenges u_j for (size_t i = 0; i < log_poly_length; i++) { std::string index = std::to_string(log_poly_length - i - 1); const auto element_L = transcript->template receive_from_prover("IPA:L_" + index); @@ -405,7 +415,7 @@ template class IPA { 0, { &msm_scalars[0], /*size*/ pippenger_size } }, { &msm_elements[0], /*size*/ pippenger_size }); GroupElement C_zero = C_prime + LR_sums; - // Step 6. + // Step 6. // Compute b_zero succinctly const Fr b_zero = evaluate_challenge_poly(round_challenges_inv, opening_claim.opening_pair.challenge); @@ -414,29 +424,69 @@ template class IPA Polynomial s_vec( construct_poly_from_u_challenges_inv(std::span(round_challenges_inv).subspan(0, log_poly_length))); + // Receive G_0 and a_0 from prover (advances transcript; G_0 not recomputed here) + Commitment G_zero_from_prover = transcript->template receive_from_prover("IPA:G_0"); + Fr a_zero = transcript->template receive_from_prover("IPA:a_0"); + + return { C_zero, b_zero, std::move(s_vec), generator_challenge, G_zero_from_prover, a_zero }; + } + + /** + * @brief Natively verify the correctness of a Proof + * + * @tparam Transcript Allows to specify a transcript class. Useful for testing + * @param vk Verification_key containing srs + * @param opening_claim Contains the commitment C and opening pair \f$(\beta, f(\beta))\f$ + * @param transcript Transcript with elements from the prover and generated challenges + * + * @return true/false depending on if the proof verifies + * + * @details The procedure runs as follows: + * + *1. Receive commitment, challenge, and claimed evaluation from the prover + *2. Receive the generator challenge \f$u\f$, abort if it's zero, otherwise compute \f$U=u\cdot G\f$ + *3. Compute \f$C'=C+f(\beta)\cdot U\f$. (Recall that \f$f(\beta)\f$ is the claimed evaluation.) + *4. Receive \f$L_j, R_j\f$ and compute challenges \f$u_j\f$ for \f$j \in {k-1,..,0}\f$, abort immediately on + receiving a \f$u_j=0\f$ + *5. Compute \f$C_0 = C' + \sum_{j=0}^{k-1}(u_j^{-1}L_j + u_jR_j)\f$ + *6. Compute \f$b_0=g(\beta)=\prod_{i=0}^{k-1}(1+u_{i}^{-1}x^{2^{i}})\f$ + *7. Compute vector \f$\vec{s}=(1,u_{0}^{-1},u_{1}^{-1},u_{0}^{-1}u_{1}^{-1},...,\prod_{i=0}^{k-1}u_{i}^{-1})\f$ + *8. Compute \f$G_s=\langle \vec{s},\vec{G}\rangle\f$ + *9. Receive \f$\vec{a}_{0}\f$ of length 1 + *10. Compute \f$C_{right}=a_{0}G_{s}+a_{0}b_{0}U\f$ + *11. Check that \f$C_{right} = C_0\f$. If they match, return true. Otherwise return false. + */ + static bool reduce_verify_internal_native(const VK& vk, const OpeningClaim& opening_claim, auto& transcript) + requires(!Curve::is_stdlib_type) + { + BB_BENCH_NAME("IPA::reduce_verify"); + + // Steps 2–7, 9: Process transcript and extract per-proof data (step 1 done by add_claim_to_hash_buffer) + auto data = read_transcript_data(opening_claim, transcript); + + // Step 8. + // Compute G_s = via SRS MSM and verify against prover's G_0 std::span srs_elements = vk.get_monomial_points(); if (poly_length > srs_elements.size()) { throw_or_abort("potential bug: Not enough SRS points for IPA!"); } - - // Step 8. - // Compute G_zero - Commitment G_zero = - scalar_multiplication::pippenger_unsafe(s_vec, { &srs_elements[0], /*size*/ poly_length }); - Commitment G_zero_sent = transcript->template receive_from_prover("IPA:G_0"); - BB_ASSERT_EQ(G_zero, G_zero_sent, "G_0 should be equal to G_0 sent in transcript. IPA verification fails."); - - // Step 9. - // Receive a_zero from the prover - auto a_zero = transcript->template receive_from_prover("IPA:a_0"); + Commitment G_zero; + { + BB_BENCH_NAME("IPA::srs_msm"); + G_zero = + scalar_multiplication::pippenger_unsafe(data.s_vec, { &srs_elements[0], /*size*/ poly_length }); + } + BB_ASSERT_EQ( + G_zero, data.G_zero_from_prover, "G_0 should be equal to G_0 sent in transcript. IPA verification fails."); // Step 10. - // Compute C_right. Implicitly, this is an IPA statement for the length 1 vectors and together with - // the URS G_0. - GroupElement right_hand_side = G_zero * a_zero + aux_generator * a_zero * b_zero; + // Compute C_right = a_0 * G_s + a_0 * b_0 * U + Commitment aux_generator = Commitment::one() * data.gen_challenge; + GroupElement right_hand_side = G_zero * data.a_zero + aux_generator * data.a_zero * data.b_zero; + // Step 11. - // Check if C_right == C_zero - return (C_zero.normalize() == right_hand_side.normalize()); + // Check if C_right == C_0 + return (data.C_zero.normalize() == right_hand_side.normalize()); } /** @@ -594,6 +644,91 @@ template class IPA return reduce_verify_internal_native(vk, opening_claim, transcript); } + /** + * @brief Batch verify multiple IPA proofs with a single large SRS MSM. + * + * @details For N proofs, IPA verification's dominant cost is the SRS MSM (pippenger over poly_length points). + * By combining N proofs via random linear combination with challenge \f$\alpha\f$, we replace N separate MSMs with + * one. + * + * The batch check verifies: + * \f$\sum \alpha^i C_{0,i} = \langle \sum \alpha^i a_{0,i} \vec{s}_i, \vec{G} \rangle + * + (\sum \alpha^i a_{0,i} b_{0,i} u_i) \cdot G\f$ + * + * where \f$G\f$ = Commitment::one() and \f$U_i = u_i \cdot G\f$. + * + * @param vk Verification key containing SRS + * @param opening_claims The opening claims for each proof + * @param transcripts The transcripts containing each proof's data + * @return true if all proofs verify + */ + template + static bool batch_reduce_verify(const VK& vk, + const std::vector>& opening_claims, + const std::vector>& transcripts) + requires(!Curve::is_stdlib_type) + { + const size_t num_claims = opening_claims.size(); + BB_ASSERT(num_claims == transcripts.size()); + BB_ASSERT(num_claims > 0); + + // Phase 1: Per-proof transcript processing (sequential, each proof is cheap) + std::vector C_zeros(num_claims); + std::vector a_zeros(num_claims); + std::vector b_zeros(num_claims); + std::vector gen_challenges(num_claims); + std::vector> s_vecs(num_claims); + + for (size_t i = 0; i < num_claims; i++) { + add_claim_to_hash_buffer(opening_claims[i], transcripts[i]); + auto data = read_transcript_data(opening_claims[i], transcripts[i]); + C_zeros[i] = std::move(data.C_zero); + b_zeros[i] = data.b_zero; + s_vecs[i] = std::move(data.s_vec); + gen_challenges[i] = data.gen_challenge; + a_zeros[i] = data.a_zero; + } + + // Phase 2: Batched computation using random challenge alpha + Fr alpha = Fr::random_element(); + std::vector alpha_pows(num_claims); + alpha_pows[0] = Fr::one(); + for (size_t i = 1; i < num_claims; i++) { + alpha_pows[i] = alpha_pows[i - 1] * alpha; + } + + // Combined s_vec: combined_s[j] = \sum \alpha^i * a_zero_i * s_vec_i[j] + Polynomial combined_s(poly_length); + for (size_t i = 0; i < num_claims; i++) { + Fr scalar = alpha_pows[i] * a_zeros[i]; + combined_s.add_scaled(s_vecs[i], scalar); + } + + // Single MSM over combined scalars + std::span srs_elements = vk.get_monomial_points(); + if (poly_length > srs_elements.size()) { + throw_or_abort("potential bug: Not enough SRS points for IPA!"); + } + Commitment G_batch = + scalar_multiplication::pippenger_unsafe(combined_s, { &srs_elements[0], /*size*/ poly_length }); + + // Combined LHS: C_batch = \sum \alpha^i * C_zero_i + GroupElement C_batch = C_zeros[0]; + for (size_t i = 1; i < num_claims; i++) { + C_batch = C_batch + C_zeros[i] * alpha_pows[i]; + } + + // Combined scalar for U terms: bU_scalar = \sum \alpha^i * a_zero_i * b_zero_i * gen_challenge_i + Fr bU_scalar = Fr::zero(); + for (size_t i = 0; i < num_claims; i++) { + bU_scalar += alpha_pows[i] * a_zeros[i] * b_zeros[i] * gen_challenges[i]; + } + + // Check: C_batch == G_batch + bU_scalar * G + GroupElement right_hand_side = G_batch + Commitment::one() * bU_scalar; + return (C_batch.normalize() == right_hand_side.normalize()); + } + /** * @brief Recursively _partially_ verify the correctness of an IPA proof. * diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.test.cpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.test.cpp index 9785be0b6792..72febbb8fd9b 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.test.cpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.test.cpp @@ -39,6 +39,22 @@ class IPATest : public CommitmentTest { vk = create_verifier_commitment_key(); } + struct ProofData { + OpeningClaim claim; + NativeTranscript::Proof proof_data; + }; + + static ProofData generate_proof(const Polynomial& poly, const Fr& x) + { + Commitment commitment = ck.commit(poly); + auto eval = poly.evaluate(x); + auto prover_transcript = std::make_shared(); + PCS::compute_opening_proof(ck, { poly, { x, eval } }, prover_transcript); + return { { { x, eval }, commitment }, prover_transcript->export_proof() }; + } + + static ProofData generate_random_proof() { return generate_proof(Polynomial::random(n), Fr::random_element()); } + struct ResultOfProveVerify { bool result; std::shared_ptr prover_transcript; @@ -49,17 +65,14 @@ class IPATest : public CommitmentTest { { Commitment commitment = ck.commit(poly); auto eval = poly.evaluate(x); - const OpeningPair opening_pair = { x, eval }; - const OpeningClaim opening_claim{ opening_pair, commitment }; - // initialize empty prover transcript auto prover_transcript = std::make_shared(); - PCS::compute_opening_proof(ck, { poly, opening_pair }, prover_transcript); + PCS::compute_opening_proof(ck, { poly, { x, eval } }, prover_transcript); // initialize verifier transcript from proof data auto verifier_transcript = std::make_shared(prover_transcript->export_proof()); // the native reduce_verify does a _complete_ IPA proof and returns whether or not the checks pass. - bool result = PCS::reduce_verify(vk, opening_claim, verifier_transcript); + bool result = PCS::reduce_verify(vk, { { x, eval }, commitment }, verifier_transcript); return { result, prover_transcript, verifier_transcript }; } }; @@ -360,5 +373,63 @@ TEST_F(IPATest, ShpleminiIPAShiftsRemoval) auto result = PCS::reduce_verify_batch_opening_claim(batch_opening_claim, vk, verifier_transcript); EXPECT_EQ(result, true); } + +// Batch IPA verification tests + +TEST_F(IPATest, BatchVerifyTwoValidProofs) +{ + auto [claim1, proof1] = generate_random_proof(); + auto [claim2, proof2] = generate_random_proof(); + + std::vector> claims = { claim1, claim2 }; + std::vector> transcripts = { std::make_shared(proof1), + std::make_shared(proof2) }; + + EXPECT_TRUE(PCS::batch_reduce_verify(vk, claims, transcripts)); +} + +TEST_F(IPATest, BatchVerifySingleProof) +{ + // Degenerate case: batch verify with N=1 should match reduce_verify + auto [claim, proof_data] = generate_random_proof(); + + EXPECT_TRUE(PCS::reduce_verify(vk, claim, std::make_shared(proof_data))); + EXPECT_TRUE(PCS::batch_reduce_verify(vk, { claim }, { std::make_shared(proof_data) })); +} + +TEST_F(IPATest, BatchVerifyTamperedProof) +{ + auto [claim1, proof1] = generate_random_proof(); + auto [claim2, proof2] = generate_random_proof(); + + // Tamper with the second claim's evaluation + claim2.opening_pair.evaluation += Fr::one(); + + std::vector> claims = { claim1, claim2 }; + std::vector> transcripts = { std::make_shared(proof1), + std::make_shared(proof2) }; + + EXPECT_FALSE(PCS::batch_reduce_verify(vk, claims, transcripts)); +} + +TEST_F(IPATest, BatchVerifyRejectsClaimTranscriptMismatch) +{ + // Batch verification must bind each claim to its own transcript. Two individually valid proofs + // should pass when correctly paired but fail when the transcripts are swapped, since each + // transcript's round messages (L_j, R_j) are coupled to its claim's commitment in C_zero. + auto [claim1, proof1] = generate_random_proof(); + auto [claim2, proof2] = generate_random_proof(); + + std::vector> claims = { claim1, claim2 }; + + // Correct pairing: passes + EXPECT_TRUE(PCS::batch_reduce_verify( + vk, claims, { std::make_shared(proof1), std::make_shared(proof2) })); + + // Swapped pairing: fails + EXPECT_FALSE(PCS::batch_reduce_verify( + vk, claims, { std::make_shared(proof2), std::make_shared(proof1) })); +} + typename IPATest::CK IPATest::ck; typename IPATest::VK IPATest::vk; diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/pairing_points.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/pairing_points.hpp index 9f2e99a904f4..86570970b692 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/pairing_points.hpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/pairing_points.hpp @@ -8,6 +8,7 @@ #include "barretenberg/commitment_schemes/verification_key.hpp" #include "barretenberg/common/assert.hpp" +#include "barretenberg/common/bb_bench.hpp" namespace bb { @@ -75,6 +76,7 @@ template class PairingPoints { */ bool check() const { + BB_BENCH_NAME("PairingPoints::check"); VerifierCK vck{}; // TODO(https://github.com/AztecProtocol/barretenberg/issues/1423): Rename to verifier_pcs_key or vckey or // something. Issue exists in many places besides just here. diff --git a/barretenberg/cpp/src/barretenberg/crypto/aes128/aes128.cpp b/barretenberg/cpp/src/barretenberg/crypto/aes128/aes128.cpp index 27626a85de50..8ac94e41164c 100644 --- a/barretenberg/cpp/src/barretenberg/crypto/aes128/aes128.cpp +++ b/barretenberg/cpp/src/barretenberg/crypto/aes128/aes128.cpp @@ -6,13 +6,12 @@ #include "aes128.hpp" +#include "barretenberg/crypto/hmac/hmac.hpp" #include "memory.h" #include #include #include -#include - namespace { static constexpr uint8_t round_constants[11] = { 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 }; @@ -248,6 +247,7 @@ void aes128_encrypt_buffer_cbc(uint8_t* buffer, uint8_t* iv, const uint8_t* key, memcpy((void*)(buffer + (i * 16)), (void*)block_state, 16); memcpy((void*)iv, (void*)block_state, 16); } + secure_erase_bytes(round_key, sizeof(round_key)); } void aes128_decrypt_buffer_cbc(uint8_t* buffer, uint8_t* iv, const uint8_t* key, const size_t length) @@ -266,6 +266,7 @@ void aes128_decrypt_buffer_cbc(uint8_t* buffer, uint8_t* iv, const uint8_t* key, memcpy((void*)(buffer + (i * 16)), (void*)block_state, 16); memcpy((void*)iv, (void*)next_iv, 16); } + secure_erase_bytes(round_key, sizeof(round_key)); } } // namespace bb::crypto \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/crypto/schnorr/multisig.hpp b/barretenberg/cpp/src/barretenberg/crypto/schnorr/multisig.hpp index 28f4403afd54..a5b3f08b710f 100644 --- a/barretenberg/cpp/src/barretenberg/crypto/schnorr/multisig.hpp +++ b/barretenberg/cpp/src/barretenberg/crypto/schnorr/multisig.hpp @@ -311,19 +311,19 @@ template cl static std::pair construct_signature_round_1() { // r_user ← 𝔽 - // TODO: securely erase `r_user` Fr r_user = Fr::random_element(); // R_user ← r_user⋅G affine_element R_user = G1::one * r_user; // s_user ← 𝔽 - // TODO: securely erase `s_user` Fr s_user = Fr::random_element(); // S_user ← s_user⋅G affine_element S_user = G1::one * s_user; RoundOnePublicOutput pubOut{ R_user, S_user }; RoundOnePrivateOutput privOut{ r_user, s_user }; + secure_erase_bytes(&r_user, sizeof(r_user)); + secure_erase_bytes(&s_user, sizeof(s_user)); return { pubOut, privOut }; } diff --git a/barretenberg/cpp/src/barretenberg/crypto/schnorr/proof_of_possession.hpp b/barretenberg/cpp/src/barretenberg/crypto/schnorr/proof_of_possession.hpp index cb17dd9ab86d..c6ad7b3592e1 100644 --- a/barretenberg/cpp/src/barretenberg/crypto/schnorr/proof_of_possession.hpp +++ b/barretenberg/cpp/src/barretenberg/crypto/schnorr/proof_of_possession.hpp @@ -48,7 +48,6 @@ template struct SchnorrProofOfPossession { // uniformly random bits. For example, when compiling into a wasm binary, it is essential that the random_get // method is overloaded to utilise a suitable entropy source // (see https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md) - // TODO: securely erase `k` Fr k = Fr::random_element(); affine_element R = G1::one * k; @@ -58,6 +57,8 @@ template struct SchnorrProofOfPossession { Fr challenge_fr = Fr::serialize_from_buffer(&challenge_bytes[0]); response = k - challenge_fr * secret_key; + secure_erase_bytes(&k, sizeof(k)); + secure_erase_bytes(&secret_key, sizeof(secret_key)); } /** diff --git a/barretenberg/cpp/src/barretenberg/crypto/schnorr/schnorr.tcc b/barretenberg/cpp/src/barretenberg/crypto/schnorr/schnorr.tcc index 0dc691ea7e3d..05e255006657 100644 --- a/barretenberg/cpp/src/barretenberg/crypto/schnorr/schnorr.tcc +++ b/barretenberg/cpp/src/barretenberg/crypto/schnorr/schnorr.tcc @@ -88,7 +88,6 @@ schnorr_signature schnorr_construct_signature(const std::string& message, const // method is overloaded to utilise a suitable entropy source // (see https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md) // - // TODO(https://github.com/AztecProtocol/barretenberg/issues/895): securely erase `k` Fr k = Fr::random_element(); typename G1::affine_element R(G1::one * k); @@ -97,6 +96,7 @@ schnorr_signature schnorr_construct_signature(const std::string& message, const // the conversion from e_raw results in a biased field element e Fr e = Fr::serialize_from_buffer(&e_raw[0]); Fr s = k - (private_key * e); + secure_erase_bytes(&k, sizeof(k)); // we serialize e_raw rather than e, so that no binary conversion needs to be // performed during verification. diff --git a/barretenberg/cpp/src/barretenberg/crypto/sha256/sha256.cpp b/barretenberg/cpp/src/barretenberg/crypto/sha256/sha256.cpp index d79d689ec02f..848bb18ddb3e 100644 --- a/barretenberg/cpp/src/barretenberg/crypto/sha256/sha256.cpp +++ b/barretenberg/cpp/src/barretenberg/crypto/sha256/sha256.cpp @@ -194,5 +194,6 @@ template Sha256Hash sha256>(const std::vector& inp template Sha256Hash sha256>(const std::array& input); template Sha256Hash sha256(const std::string& input); template Sha256Hash sha256>(const std::span& input); +template Sha256Hash sha256>(const std::span& input); } // namespace bb::crypto diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/gate_count_constants.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/gate_count_constants.hpp index 2e9442c984dc..26c9d557473e 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/gate_count_constants.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/gate_count_constants.hpp @@ -113,7 +113,7 @@ constexpr std::tuple HONK_RECURSION_CONSTANTS( // ======================================== // Gate count for Chonk recursive verification (Ultra with RollupIO) -inline constexpr size_t CHONK_RECURSION_GATES = 1684546; +inline constexpr size_t CHONK_RECURSION_GATES = 1685112; // ======================================== // Hypernova Recursion Constants @@ -147,7 +147,7 @@ inline constexpr size_t HIDING_KERNEL_ULTRA_OPS = 124; // ======================================== // Gate count for ECCVM recursive verifier (Ultra-arithmetized) -inline constexpr size_t ECCVM_RECURSIVE_VERIFIER_GATE_COUNT = 224139; +inline constexpr size_t ECCVM_RECURSIVE_VERIFIER_GATE_COUNT = 224707; // ======================================== // Goblin AVM Recursive Verifier Constants diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/bn254.hpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/bn254.hpp index 0c94dcdba448..46e09ad54475 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/bn254.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/bn254.hpp @@ -1,5 +1,5 @@ // === AUDIT STATUS === -// internal: { status: Planned, auditors: [], commit: } +// internal: { status: Completed, auditors: [Federico], commit: } // external_1: { status: not started, auditors: [], commit: } // external_2: { status: not started, auditors: [], commit: } // ===================== @@ -25,9 +25,6 @@ class BN254 { using TargetField = bb::fq12; static constexpr const char* name = "BN254"; - // TODO(#673): This flag is temporary. It is needed in the verifier classes (GeminiVerifier, etc.) while these - // classes are instantiated with "native" curve types. Eventually, the verifier classes will be instantiated only - // with stdlib types, and "native" verification will be acheived via a simulated builder. static constexpr bool is_stdlib_type = false; // Required by SmallSubgroupIPA argument. This constant needs to divide the size of the multiplicative subgroup of @@ -35,8 +32,9 @@ class BN254 { // each BN254-Flavor, since in every round of Sumcheck, the prover sends Flavor::BATCHED_RELATION_PARTIAL_LENGTH // elements to the verifier. static constexpr size_t SUBGROUP_SIZE = 256; - // BN254's scalar field has a multiplicative subgroup of order 2^28. It is generated by 5. The generator below is - // 5^{2^{20}}. To avoid inversion in the recursive verifier, we also store the inverse of the chosen generator. + // BN254's scalar field has a multiplicative subgroup of order 2^28. It is generated by 5^{(r-1) / 2^28}. The + // generator below is 5^{(r-1) / 2^8} (so it has order 8). To avoid inversion in the recursive verifier, we also + // store the inverse of the chosen generator. static constexpr ScalarField subgroup_generator = ScalarField(uint256_t("0x07b0c561a6148404f086204a9f36ffb0617942546750f230c893619174a57a76")); static constexpr ScalarField subgroup_generator_inverse = diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/bn254.test.cpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/bn254.test.cpp new file mode 100644 index 000000000000..7e28079a3786 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/bn254.test.cpp @@ -0,0 +1,25 @@ +/** + * @brief Tests that verify the correctness of BN-254 field constants + * + */ +#include "bn254.hpp" +#include "barretenberg/numeric/uint256/uint256.hpp" +#include +#include + +using namespace bb; + +// ================================ +// BN254 Constants Tests +// ================================ + +TEST(Bn254Constants, SubgroupGenerator) +{ + fr subgroup_generator = bb::curve::BN254::subgroup_generator; + fr subgroup_generator_inverse = bb::curve::BN254::subgroup_generator_inverse; + fr expected = fr(5).pow((fr::modulus - 1) / (uint256_t(1) << 8)); + fr expected_inverse = expected.invert(); + + EXPECT_EQ(subgroup_generator, expected); + EXPECT_EQ(subgroup_generator_inverse, expected_inverse); +} diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/constants.test.cpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/constants.test.cpp deleted file mode 100644 index 8ea794633a3d..000000000000 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/constants.test.cpp +++ /dev/null @@ -1,289 +0,0 @@ -/** - * @brief Tests that verify the correctness of BN-254 field constants (both Fq and Fr). - * - */ -#include "barretenberg/numeric/random/engine.hpp" -#include "barretenberg/numeric/uint256/uint256.hpp" -#include "barretenberg/serialize/test_helper.hpp" -#include "fq.hpp" -#include "fr.hpp" -#include -#include - -using namespace bb; -namespace { -uint256_t native_q{ - Bn254FqParams::modulus_0, Bn254FqParams::modulus_1, Bn254FqParams::modulus_2, Bn254FqParams::modulus_3 -}; -uint256_t native_r{ - Bn254FrParams::modulus_0, Bn254FrParams::modulus_1, Bn254FrParams::modulus_2, Bn254FrParams::modulus_3 -}; - -// Helper to convert a decimal string to uint256_t -uint256_t from_decimal(const std::string& dec_str) -{ - uint256_t result = 0; - for (char c : dec_str) { - result = result * 10 + static_cast(c - '0'); - } - return result; -} -} // namespace - -TEST(FqConstants, Modulus) -{ - // BN254 base field prime: q = 21888242871839275222246405745257275088696311157297823662689037894645226208583 - // References: - // [eip-196](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-196.md) - // [ark-works](https://docs.rs/ark-bn254/latest/ark_bn254/) - // [BN-254 for the rest of us](https://hackmd.io/@jpw/bn254) - uint256_t expected_q = - from_decimal("21888242871839275222246405745257275088696311157297823662689037894645226208583"); - EXPECT_EQ(expected_q, native_q); -} - -TEST(FqConstants, RSquared) -{ - // R^2 = (2^256)^2 mod q. - uint512_t R = (uint512_t(1) << 256) % native_q; - uint512_t expected_r_sqr_mod_q = (R * R) % native_q; - uint256_t actual_r_sqr_mod_q{ - Bn254FqParams::r_squared_0, Bn254FqParams::r_squared_1, Bn254FqParams::r_squared_2, Bn254FqParams::r_squared_3 - }; - EXPECT_EQ(expected_r_sqr_mod_q.lo, actual_r_sqr_mod_q); -} - -TEST(FqConstants, RInv) -{ - // r_inv = -q^{-1} mod 2^64 - uint512_t r{ 0, 1 }; - uint512_t q{ -native_q, 0 }; - uint256_t q_inv = q.invmod(r).lo; - uint64_t expected = q_inv.data[0]; - uint64_t result = Bn254FqParams::r_inv; - EXPECT_EQ(result, expected); -} - -TEST(FqConstants, CubeRootOfUnity) -{ - fq beta = fq::cube_root_of_unity(); - - // Verify beta^3 = 1 and beta != 1 - EXPECT_EQ(beta * beta * beta, fq::one()); - EXPECT_NE(beta, fq::one()); -} - -// ================================ -// WASM Consistency Tests -// ================================ - -TEST(FqConstants, WasmModulusConsistency) -{ - // WASM uses 9 x 29-bit limbs to represent the modulus - // Verify that the 29-bit limb representation reconstructs to the same value as the 4 x 64-bit limb representation - constexpr std::array wasm_limbs = { Bn254FqParams::modulus_wasm_0, Bn254FqParams::modulus_wasm_1, - Bn254FqParams::modulus_wasm_2, Bn254FqParams::modulus_wasm_3, - Bn254FqParams::modulus_wasm_4, Bn254FqParams::modulus_wasm_5, - Bn254FqParams::modulus_wasm_6, Bn254FqParams::modulus_wasm_7, - Bn254FqParams::modulus_wasm_8 }; - - uint512_t wasm_modulus = 0; - for (size_t i = 0; i < 9; i++) { - wasm_modulus += uint512_t(wasm_limbs[i]) << (29UL * i); - // Verify each limb fits in 29 bits - EXPECT_LT(wasm_limbs[i], uint64_t(1) << 29); - } - - EXPECT_EQ(wasm_modulus.lo, native_q); - EXPECT_EQ(wasm_modulus.hi, uint256_t(0)); -} - -TEST(FqConstants, WasmRSquared) -{ - // WASM uses R = 2^261 (since 261 = 29 * 9) - // r_squared_wasm should be (2^261)^2 mod q = 2^522 mod q - uint512_t R_wasm = uint512_t(1) << 261; - uint512_t R_wasm_mod_q = R_wasm % native_q; - uint512_t expected_r_squared_wasm = (R_wasm_mod_q * R_wasm_mod_q) % native_q; - - uint256_t actual_r_squared_wasm{ Bn254FqParams::r_squared_wasm_0, - Bn254FqParams::r_squared_wasm_1, - Bn254FqParams::r_squared_wasm_2, - Bn254FqParams::r_squared_wasm_3 }; - - EXPECT_EQ(expected_r_squared_wasm.lo, actual_r_squared_wasm); -} - -TEST(FqConstants, WasmCubeRootConsistency) -{ - // The cube root in WASM Montgomery form should represent the same field element - // as the cube root in native Montgomery form. - // - // Native Montgomery form: cube_root_native = beta * R_native mod q, where R_native = 2^256 - // WASM Montgomery form: cube_root_wasm = beta * R_wasm mod q, where R_wasm = 2^261 - // - // Therefore: cube_root_wasm = cube_root_native * (R_wasm / R_native) mod q - // = cube_root_native * 2^5 mod q - - uint256_t cube_root_native{ - Bn254FqParams::cube_root_0, Bn254FqParams::cube_root_1, Bn254FqParams::cube_root_2, Bn254FqParams::cube_root_3 - }; - - uint256_t cube_root_wasm{ Bn254FqParams::cube_root_wasm_0, - Bn254FqParams::cube_root_wasm_1, - Bn254FqParams::cube_root_wasm_2, - Bn254FqParams::cube_root_wasm_3 }; - - // R_wasm / R_native = 2^261 / 2^256 = 2^5 = 32 - uint512_t expected_cube_root_wasm = (uint512_t(cube_root_native) * 32) % native_q; - - EXPECT_EQ(expected_cube_root_wasm.lo, cube_root_wasm); -} -// r_inv_wasm represents 2^(-29) mod q in 9 x 29-bit limbs -// this tests checks that that r_inv_wasm < q/2 (and in particular less than q). -TEST(FqConstants, WasmRInvLessThanModulus) -{ - // Verify that when reconstructed as a uint512_t, it is less than the modulus q - constexpr std::array r_inv_wasm_limbs = { Bn254FqParams::r_inv_wasm_0, Bn254FqParams::r_inv_wasm_1, - Bn254FqParams::r_inv_wasm_2, Bn254FqParams::r_inv_wasm_3, - Bn254FqParams::r_inv_wasm_4, Bn254FqParams::r_inv_wasm_5, - Bn254FqParams::r_inv_wasm_6, Bn254FqParams::r_inv_wasm_7, - Bn254FqParams::r_inv_wasm_8 }; - - uint512_t r_inv_wasm = 0; - for (size_t i = 0; i < 9; i++) { - r_inv_wasm += uint512_t(r_inv_wasm_limbs[i]) << (29UL * i); - // Verify each limb fits in 29 bits - EXPECT_LT(r_inv_wasm_limbs[i], uint64_t(1) << 29); - } - - // Verify r_inv_wasm < q/2 - EXPECT_LT(r_inv_wasm, uint512_t(native_q) / 2); -} - -// ================================ -// Fr Constants Tests -// ================================ - -TEST(FrConstants, Modulus) -{ - // BN254 scalar field prime (also the Baby Jubjub base field): - // r = 21888242871839275222246405745257275088548364400416034343698204186575808495617 - // References: - // [eip-196](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-196.md) - // [ark-works](https://docs.rs/ark-bn254/latest/ark_bn254/) - // [BN-254 for the rest of us](https://hackmd.io/@jpw/bn254) - uint256_t expected_r = - from_decimal("21888242871839275222246405745257275088548364400416034343698204186575808495617"); - EXPECT_EQ(expected_r, native_r); -} - -TEST(FrConstants, RSquared) -{ - // R^2 = (2^256)^2 mod r. - uint512_t R = (uint512_t(1) << 256) % native_r; - uint512_t expected_r_sqr_mod_r = (R * R) % native_r; - uint256_t actual_r_sqr_mod_r{ - Bn254FrParams::r_squared_0, Bn254FrParams::r_squared_1, Bn254FrParams::r_squared_2, Bn254FrParams::r_squared_3 - }; - EXPECT_EQ(expected_r_sqr_mod_r.lo, actual_r_sqr_mod_r); -} - -TEST(FrConstants, RInv) -{ - // r_inv = -r^{-1} mod 2^64 - uint512_t two_64{ 0, 1 }; - uint512_t neg_r{ -native_r, 0 }; - uint256_t r_inv = neg_r.invmod(two_64).lo; - uint64_t expected = r_inv.data[0]; - uint64_t result = Bn254FrParams::r_inv; - EXPECT_EQ(result, expected); -} - -TEST(FrConstants, CubeRootOfUnity) -{ - fr beta = fr::cube_root_of_unity(); - - // Verify beta^3 = 1 and beta != 1 - EXPECT_EQ(beta * beta * beta, fr::one()); - EXPECT_NE(beta, fr::one()); -} - -// ================================ -// Fr WASM Consistency Tests -// ================================ - -TEST(FrConstants, WasmModulusConsistency) -{ - // WASM uses 9 x 29-bit limbs to represent the modulus - constexpr std::array wasm_limbs = { Bn254FrParams::modulus_wasm_0, Bn254FrParams::modulus_wasm_1, - Bn254FrParams::modulus_wasm_2, Bn254FrParams::modulus_wasm_3, - Bn254FrParams::modulus_wasm_4, Bn254FrParams::modulus_wasm_5, - Bn254FrParams::modulus_wasm_6, Bn254FrParams::modulus_wasm_7, - Bn254FrParams::modulus_wasm_8 }; - - uint512_t wasm_modulus = 0; - for (size_t i = 0; i < 9; i++) { - wasm_modulus += uint512_t(wasm_limbs[i]) << (29UL * i); - EXPECT_LT(wasm_limbs[i], uint64_t(1) << 29); - } - - EXPECT_EQ(wasm_modulus.lo, native_r); - EXPECT_EQ(wasm_modulus.hi, uint256_t(0)); -} - -TEST(FrConstants, WasmRSquared) -{ - // WASM uses R = 2^261 (since 261 = 29 * 9) - uint512_t R_wasm = uint512_t(1) << 261; - uint512_t R_wasm_mod_r = R_wasm % native_r; - uint512_t expected_r_squared_wasm = (R_wasm_mod_r * R_wasm_mod_r) % native_r; - - uint256_t actual_r_squared_wasm{ Bn254FrParams::r_squared_wasm_0, - Bn254FrParams::r_squared_wasm_1, - Bn254FrParams::r_squared_wasm_2, - Bn254FrParams::r_squared_wasm_3 }; - - EXPECT_EQ(expected_r_squared_wasm.lo, actual_r_squared_wasm); -} - -TEST(FrConstants, WasmCubeRootConsistency) -{ - // The cube root in WASM Montgomery form should represent the same field element - // as the cube root in native Montgomery form. - uint256_t cube_root_native{ - Bn254FrParams::cube_root_0, Bn254FrParams::cube_root_1, Bn254FrParams::cube_root_2, Bn254FrParams::cube_root_3 - }; - - uint256_t cube_root_wasm{ Bn254FrParams::cube_root_wasm_0, - Bn254FrParams::cube_root_wasm_1, - Bn254FrParams::cube_root_wasm_2, - Bn254FrParams::cube_root_wasm_3 }; - - // R_wasm / R_native = 2^261 / 2^256 = 2^5 = 32 - uint512_t expected_cube_root_wasm = (uint512_t(cube_root_native) * 32) % native_r; - - EXPECT_EQ(expected_cube_root_wasm.lo, cube_root_wasm); -} - -// r_inv_wasm represents 2^(-29) mod r in 9 x 29-bit limbs -// this tests verifies that r_inv_wasm < r/2. -TEST(FrConstants, WasmRInvLessThanModulus) -{ - // Verify that when reconstructed as a uint512_t, it is less than the modulus r - constexpr std::array r_inv_wasm_limbs = { Bn254FrParams::r_inv_wasm_0, Bn254FrParams::r_inv_wasm_1, - Bn254FrParams::r_inv_wasm_2, Bn254FrParams::r_inv_wasm_3, - Bn254FrParams::r_inv_wasm_4, Bn254FrParams::r_inv_wasm_5, - Bn254FrParams::r_inv_wasm_6, Bn254FrParams::r_inv_wasm_7, - Bn254FrParams::r_inv_wasm_8 }; - - uint512_t r_inv_wasm = 0; - for (size_t i = 0; i < 9; i++) { - r_inv_wasm += uint512_t(r_inv_wasm_limbs[i]) << (29UL * i); - // Verify each limb fits in 29 bits - EXPECT_LT(r_inv_wasm_limbs[i], uint64_t(1) << 29); - } - - // Verify r_inv_wasm < r/2 - EXPECT_LT(r_inv_wasm, uint512_t(native_r) / 2); -} diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq.hpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq.hpp index 245ce83fac99..2f66bc815016 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq.hpp @@ -1,5 +1,5 @@ // === AUDIT STATUS === -// internal: { status: Planned, auditors: [], commit: } +// internal: { status: Completed, auditors: [Federico], commit: } // external_1: { status: not started, auditors: [], commit: } // external_2: { status: not started, auditors: [], commit: } // ===================== @@ -16,9 +16,16 @@ // NOLINTBEGIN(cppcoreguidelines-avoid-c-arrays) namespace bb { +/** + * @brief Parameters defining the base field of the BN254 curve. + * + * @details When split into 4 64-bit words, the parameters are represented in little-endian, i.e. the least significant + * bit comes first. For example, to recover the modulus from the 64-bit words we concatenate its limbs to obtain: + * 0x30644e72e131a029b85045b68181585d97816a916871ca8d3C208C16D87CFD47 + * + * @note These parameters can be extracted by running the script parameter_helper.py in ecc/fields + */ class Bn254FqParams { - // There is a helper script in ecc/fields/parameter_helper.py that can be used to extract these parameters from the - // source code public: // A little-endian representation of the modulus split into 4 64-bit words static constexpr uint64_t modulus_0 = 0x3C208C16D87CFD47UL; @@ -33,12 +40,40 @@ class Bn254FqParams { static constexpr uint64_t r_squared_2 = 0x47AB1EFF0A417FF6UL; static constexpr uint64_t r_squared_3 = 0x06D89F71CAB8351FUL; + // -(Modulus^-1) mod 2^64 + // This constant is used during multiplication: given an 8-limb representation of the multiplication of two field + // elements, for each of the lowest four limbs we compute: k_i = r_inv * limb_i and we add 2^{64 * i} * k_i * p to + // the result of the multiplication. In this way we zero out the lowest four limbs of the multiplication and we can + // divide by 2^256 by taking the highest four limbs. See field_docs.hpp for more details. + static constexpr uint64_t r_inv = 0x87d20782e4866389UL; + + // 2^(-64) mod Modulus + // Used in the reduction mechanism, see field_docs.md + // Instead of computing k, we multiply the lowest limb by this value and then add to the following 5 limbs. + // This saves us from having to compute k + static constexpr uint64_t r_inv_0 = 0x327d7c1b18f7bd41UL; + static constexpr uint64_t r_inv_1 = 0xdb8ed52f824ed32fUL; + static constexpr uint64_t r_inv_2 = 0x29b67b05eb29a6a1UL; + static constexpr uint64_t r_inv_3 = 0x19ac99126b459ddaUL; + // A little-endian representation of the cube root of 1 in Fq in Montgomery form split into 4 64-bit words static constexpr uint64_t cube_root_0 = 0x71930c11d782e155UL; static constexpr uint64_t cube_root_1 = 0xa6bb947cffbe3323UL; static constexpr uint64_t cube_root_2 = 0xaa303344d4741444UL; static constexpr uint64_t cube_root_3 = 0x2c3b3f0d26594943UL; + // Not used for Fq, but required for all field types + static constexpr uint64_t primitive_root_0 = 0UL; + static constexpr uint64_t primitive_root_1 = 0UL; + static constexpr uint64_t primitive_root_2 = 0UL; + static constexpr uint64_t primitive_root_3 = 0UL; + + // Coset generators in Montgomery form for R=2^256 mod Modulus. Used in FFT-based proving systems + static constexpr uint64_t coset_generator_0 = 0x7a17caa950ad28d7ULL; + static constexpr uint64_t coset_generator_1 = 0x1f6ac17ae15521b9ULL; + static constexpr uint64_t coset_generator_2 = 0x334bea4e696bd284ULL; + static constexpr uint64_t coset_generator_3 = 0x2a1f6744ce179d8eULL; + // A little-endian representation of the modulus split into 9 29-bit limbs // This is used in wasm because we can only do multiplication with 64-bit result instead of 128-bit like in x86_64 static constexpr uint64_t modulus_wasm_0 = 0x187cfd47; @@ -58,6 +93,20 @@ class Bn254FqParams { static constexpr uint64_t r_squared_wasm_2 = 0xff54c5802d3e2632UL; static constexpr uint64_t r_squared_wasm_3 = 0x2a11a68c34ea65a6UL; + // 2^(-29) mod Modulus + // Used in the reduction mechanism, see field_docs.md + // Instead of computing k, we multiply the lowest limb by this value and then add to the following 10 limbs. + // This saves us from having to compute k + static constexpr uint64_t r_inv_wasm_0 = 0x17789a9f; + static constexpr uint64_t r_inv_wasm_1 = 0x5ffc3dc; + static constexpr uint64_t r_inv_wasm_2 = 0xd6bde42; + static constexpr uint64_t r_inv_wasm_3 = 0x1cf152e3; + static constexpr uint64_t r_inv_wasm_4 = 0x18eb055f; + static constexpr uint64_t r_inv_wasm_5 = 0xed815e2; + static constexpr uint64_t r_inv_wasm_6 = 0x16626d2; + static constexpr uint64_t r_inv_wasm_7 = 0xb8bab0f; + static constexpr uint64_t r_inv_wasm_8 = 0x6d7c4; + // A little-endian representation of the cube root of 1 in Fq in Montgomery form for wasm (R=2^261 mod modulus) // split into 4 64-bit words static constexpr uint64_t cube_root_wasm_0 = 0x62b1a3a46a337995UL; @@ -65,20 +114,20 @@ class Bn254FqParams { static constexpr uint64_t cube_root_wasm_2 = 0x64ee82ede2db85faUL; static constexpr uint64_t cube_root_wasm_3 = 0x0c0afea1488a03bbUL; - // Not used for Fq, but required for all field types - static constexpr uint64_t primitive_root_0 = 0UL; - static constexpr uint64_t primitive_root_1 = 0UL; - static constexpr uint64_t primitive_root_2 = 0UL; - static constexpr uint64_t primitive_root_3 = 0UL; - // Not used for Fq, but required for all field types static constexpr uint64_t primitive_root_wasm_0 = 0x0000000000000000UL; static constexpr uint64_t primitive_root_wasm_1 = 0x0000000000000000UL; static constexpr uint64_t primitive_root_wasm_2 = 0x0000000000000000UL; static constexpr uint64_t primitive_root_wasm_3 = 0x0000000000000000UL; + // Coset generators in Montgomery form for R=2^261 mod Modulus. Used in FFT-based proving systems + static constexpr uint64_t coset_generator_wasm_0 = 0xeb8a8ec140766463ULL; + static constexpr uint64_t coset_generator_wasm_1 = 0xf2b1f20626a3da49ULL; + static constexpr uint64_t coset_generator_wasm_2 = 0xf905ef8d84d5fea4ULL; + static constexpr uint64_t coset_generator_wasm_3 = 0x2958a27c02b7cd5fULL; + // Parameters used for quickly splitting a scalar into two endomorphism scalars for faster scalar multiplication - // For specifics on how these have been derived, ask @zac-williamson + // For specifics on how these have been derived, see ecc/fields/endomorphim_scalars.py static constexpr uint64_t endo_g1_lo = 0x7a7bd9d4391eb18d; static constexpr uint64_t endo_g1_mid = 0x4ccef014a773d2cfUL; static constexpr uint64_t endo_g1_hi = 0x0000000000000002UL; @@ -89,72 +138,6 @@ class Bn254FqParams { static constexpr uint64_t endo_b2_lo = 0x89d3256894d213e2UL; static constexpr uint64_t endo_b2_mid = 0UL; - // -(Modulus^-1) mod 2^64 - // This is used to compute k = r_inv * lower_limb(scalar), such that scalar + k*modulus in integers would have 0 in - // the lowest limb. By performing this sequentially for 4 limbs, we get an 8-limb representation of the scalar, - // where the lowest 4 limbs are zeros. Then we can immediately divide by 2^256 by simply getting rid of the lowest 4 - // limbs - static constexpr uint64_t r_inv = 0x87d20782e4866389UL; - - // 2^(-64) mod Modulus - // Used in the reduction mechanism from https://hackmd.io/@Ingonyama/Barret-Montgomery - // Instead of computing k, we multiply the lowest limb by this value and then add to the following 5 limbs. - // This saves us from having to compute k - static constexpr uint64_t r_inv_0 = 0x327d7c1b18f7bd41UL; - static constexpr uint64_t r_inv_1 = 0xdb8ed52f824ed32fUL; - static constexpr uint64_t r_inv_2 = 0x29b67b05eb29a6a1UL; - static constexpr uint64_t r_inv_3 = 0x19ac99126b459ddaUL; - - // 2^(-29) mod Modulus - // Used in the reduction mechanism from https://hackmd.io/@Ingonyama/Barret-Montgomery - // Instead of computing k, we multiply the lowest limb by this value and then add to the following 10 limbs. - // This saves us from having to compute k - static constexpr uint64_t r_inv_wasm_0 = 0x17789a9f; - static constexpr uint64_t r_inv_wasm_1 = 0x5ffc3dc; - static constexpr uint64_t r_inv_wasm_2 = 0xd6bde42; - static constexpr uint64_t r_inv_wasm_3 = 0x1cf152e3; - static constexpr uint64_t r_inv_wasm_4 = 0x18eb055f; - static constexpr uint64_t r_inv_wasm_5 = 0xed815e2; - static constexpr uint64_t r_inv_wasm_6 = 0x16626d2; - static constexpr uint64_t r_inv_wasm_7 = 0xb8bab0f; - static constexpr uint64_t r_inv_wasm_8 = 0x6d7c4; - - // Coset generators in Montgomery form for R=2^256 mod Modulus. Used in FFT-based proving systems - static constexpr uint64_t coset_generators_0[8]{ - 0x7a17caa950ad28d7ULL, 0x4d750e37163c3674ULL, 0x20d251c4dbcb4411ULL, 0xf42f9552a15a51aeULL, - 0x4f4bc0b2b5ef64bdULL, 0x22a904407b7e725aULL, 0xf60647ce410d7ff7ULL, 0xc9638b5c069c8d94ULL, - }; - static constexpr uint64_t coset_generators_1[8]{ - 0x1f6ac17ae15521b9ULL, 0x29e3aca3d71c2cf7ULL, 0x345c97cccce33835ULL, 0x3ed582f5c2aa4372ULL, - 0x1a4b98fbe78db996ULL, 0x24c48424dd54c4d4ULL, 0x2f3d6f4dd31bd011ULL, 0x39b65a76c8e2db4fULL, - }; - static constexpr uint64_t coset_generators_2[8]{ - 0x334bea4e696bd284ULL, 0x99ba8dbde1e518b0ULL, 0x29312d5a5e5edcULL, 0x6697d49cd2d7a508ULL, - 0x5c65ec9f484e3a79ULL, 0xc2d4900ec0c780a5ULL, 0x2943337e3940c6d1ULL, 0x8fb1d6edb1ba0cfdULL, - }; - static constexpr uint64_t coset_generators_3[8]{ - 0x2a1f6744ce179d8eULL, 0x3829df06681f7cbdULL, 0x463456c802275bedULL, 0x543ece899c2f3b1cULL, - 0x180a96573d3d9f8ULL, 0xf8b21270ddbb927ULL, 0x1d9598e8a7e39857ULL, 0x2ba010aa41eb7786ULL, - }; - - // Coset generators in Montgomery form for R=2^261 mod Modulus. Used in FFT-based proving systems - static constexpr uint64_t coset_generators_wasm_0[8] = { 0xeb8a8ec140766463ULL, 0xfded87957d76333dULL, - 0x4c710c8092f2ff5eULL, 0x9af4916ba86fcb7fULL, - 0xe9781656bdec97a0ULL, 0xfbdb0f2afaec667aULL, - 0x4a5e94161069329bULL, 0x98e2190125e5febcULL }; - static constexpr uint64_t coset_generators_wasm_1[8] = { 0xf2b1f20626a3da49ULL, 0x56c12d76cb13587fULL, - 0x5251d378d7f4a143ULL, 0x4de2797ae4d5ea06ULL, - 0x49731f7cf1b732c9ULL, 0xad825aed9626b0ffULL, - 0xa91300efa307f9c3ULL, 0xa4a3a6f1afe94286ULL }; - static constexpr uint64_t coset_generators_wasm_2[8] = { 0xf905ef8d84d5fea4ULL, 0x93b7a45b84f1507eULL, - 0xe6b99ee0068dfab5ULL, 0x39bb9964882aa4ecULL, - 0x8cbd93e909c74f23ULL, 0x276f48b709e2a0fcULL, - 0x7a71433b8b7f4b33ULL, 0xcd733dc00d1bf56aULL }; - static constexpr uint64_t coset_generators_wasm_3[8] = { 0x2958a27c02b7cd5fULL, 0x06bc8a3277c371abULL, - 0x1484c05bce00b620ULL, 0x224cf685243dfa96ULL, - 0x30152cae7a7b3f0bULL, 0x0d791464ef86e357ULL, - 0x1b414a8e45c427ccULL, 0x290980b79c016c41ULL }; - // used in msgpack schema serialization static constexpr char schema_name[] = "fq"; static constexpr bool has_high_2adicity = false; diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq.test.cpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq.test.cpp index 87770420a582..5ac7c972b88e 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq.test.cpp @@ -3,8 +3,8 @@ * * Other field arithmetic tests (both compile-time and runtime) are in ecc/fields/generic_field.test.cpp and * ecc/fields/prime_field.test.cpp. This file contains only BN254-specific functionality: + * - Fixed compile-time tests with field-specific expected values * - Endomorphism scalar decomposition - * - Buffer serialization (tests specific byte layout) * - Regression tests for specific values */ @@ -28,10 +28,9 @@ auto& engine = numeric::get_debug_randomness(); #if defined(__SIZEOF_INT128__) && !defined(__wasm__) TEST(BN254Fq, CompileTimeMultiplication) { - constexpr fq a = uint256_t{ 0xa9b879029c49e60eUL, 0x2517b72250caa7b3UL, 0x6b86c81105dae2d1UL, 0x3a81735d5aec0c3UL }; - constexpr fq b = uint256_t{ 0x744fc10aec23e56aUL, 0x5dea4788a3b936a6UL, 0xa0a89f4a8af01df1UL, 0x72ae28836807df3UL }; - constexpr fq expected = - uint256_t{ 0x6c0a789c0028fd09UL, 0xca9520d84c684efaUL, 0xcbf3f7b023a852b4UL, 0x1b2e4dac41400621UL }; + constexpr fq a{ 0x83aa80986c4f06f8, 0xbd01cce5e3b3afc3, 0x1cba208cb70aa13b, 0x2a582eb35a932e0d }; + constexpr fq b{ 0x348ea47f1840a528, 0x5e6eb8e57e1b246d, 0x10852d3d36002e53, 0x280130d2f6a97aba }; + constexpr fq expected{ 0x67eaddc2ba233427, 0x3c4f7dfe46ef24a9, 0x8fecb77e2ff74d64, 0x275537b321138ee7 }; constexpr fq result = a * b; static_assert(result == expected); @@ -39,9 +38,8 @@ TEST(BN254Fq, CompileTimeMultiplication) TEST(BN254Fq, CompileTimeSquaring) { - constexpr fq a = uint256_t{ 0xa9b879029c49e60eUL, 0x2517b72250caa7b3UL, 0x6b86c81105dae2d1UL, 0x3a81735d5aec0c3UL }; - constexpr fq expected = - uint256_t{ 0x41081a42fdaa7e23UL, 0x44d1140f756ed419UL, 0x53716b0a6f253e63UL, 0xb1a0b04044d75fUL }; + constexpr fq a{ 0x83aa80986c4f06f8, 0xbd01cce5e3b3afc3, 0x1cba208cb70aa13b, 0x2a582eb35a932e0d }; + constexpr fq expected{ 0xe441c0408a6fab60, 0xb94616ade6ed8752, 0x36cb53ba8e85397f, 0x17698305ec38b773 }; constexpr fq result = a.sqr(); static_assert(result == expected); @@ -70,158 +68,12 @@ TEST(BN254Fq, CompileTimeSubtraction) TEST(BN254Fq, CompileTimeInversion) { - constexpr fq a = uint256_t{ 0xa9b879029c49e60eUL, 0x2517b72250caa7b3UL, 0x6b86c81105dae2d1UL, 0x3a81735d5aec0c3UL }; + constexpr fq a{ 0x83aa80986c4f06f8, 0xbd01cce5e3b3afc3, 0x1cba208cb70aa13b, 0x2a582eb35a932e0d }; constexpr fq inv = a.invert(); // Verify a * a^-1 = 1 static_assert(a * inv == fq::one()); } -// ================================ -// Montgomery Form -// ================================ - -TEST(BN254Fq, FromMontgomeryForm) -{ - constexpr fq t0 = fq::one(); - constexpr fq result = t0.from_montgomery_form(); - constexpr fq expected{ 0x01, 0x00, 0x00, 0x00 }; - EXPECT_EQ(result, expected); -} - -TEST(BN254Fq, MontgomeryConsistencyCheck) -{ - fq a = fq::random_element(); - fq b = fq::random_element(); - fq aR; - fq bR; - fq aRR; - fq bRR; - fq bRRR; - fq result_a; - fq result_b; - fq result_c; - fq result_d; - aR = a.to_montgomery_form(); - aRR = aR.to_montgomery_form(); - bR = b.to_montgomery_form(); - bRR = bR.to_montgomery_form(); - bRRR = bRR.to_montgomery_form(); - result_a = aRR * bRR; // abRRR - result_b = aR * bRRR; // abRRR - result_c = aR * bR; // abR - result_d = a * b; // abR^-1 - EXPECT_EQ((result_a == result_b), true); - result_a.self_from_montgomery_form(); // abRR - result_a.self_from_montgomery_form(); // abR - result_a.self_from_montgomery_form(); // ab - result_c.self_from_montgomery_form(); // ab - result_d.self_to_montgomery_form(); // ab - EXPECT_EQ((result_a == result_c), true); - EXPECT_EQ((result_a == result_d), true); -} - -// ================================ -// Arithmetic Consistency -// ================================ - -TEST(BN254Fq, AddMulConsistency) -{ - fq multiplicand = { 0x09, 0, 0, 0 }; - multiplicand.self_to_montgomery_form(); - - fq a = fq::random_element(); - fq result; - result = a + a; // 2 - result += result; // 4 - result += result; // 8 - result += a; // 9 - - fq expected; - expected = a * multiplicand; - - EXPECT_EQ((result == expected), true); -} - -TEST(BN254Fq, SubMulConsistency) -{ - fq multiplicand = { 0x05, 0, 0, 0 }; - multiplicand.self_to_montgomery_form(); - - fq a = fq::random_element(); - fq result; - result = a + a; // 2 - result += result; // 4 - result += result; // 8 - result -= a; // 7 - result -= a; // 6 - result -= a; // 5 - - fq expected; - expected = a * multiplicand; - - EXPECT_EQ((result == expected), true); -} - -TEST(BN254Fq, Invert) -{ - fq input = fq::random_element(); - fq inverse = input.invert(); - fq result = input * inverse; - result = result.reduce_once(); - result = result.reduce_once(); - EXPECT_EQ(result, fq::one()); -} - -TEST(BN254Fq, InvertOneIsOne) -{ - fq result = fq::one(); - result = result.invert(); - EXPECT_EQ((result == fq::one()), true); -} - -TEST(BN254Fq, Sqrt) -{ - fq input = fq::one(); - auto [is_sqr, root] = input.sqrt(); - fq result = root.sqr(); - EXPECT_EQ(result, input); -} - -TEST(BN254Fq, SqrtRandom) -{ - for (size_t i = 0; i < 1; ++i) { - fq input = fq::random_element().sqr(); - auto [is_sqr, root] = input.sqrt(); - fq root_test = root.sqr(); - EXPECT_EQ(root_test, input); - } -} - -TEST(BN254Fq, OneAndZero) -{ - fq result; - result = fq::one() - fq::one(); - EXPECT_EQ((result == fq::zero()), true); -} - -TEST(BN254Fq, Copy) -{ - fq result = fq::random_element(); - fq expected; - fq::__copy(result, expected); - EXPECT_EQ((result == expected), true); -} - -TEST(BN254Fq, Neg) -{ - fq a = fq::random_element(); - fq b; - b = -a; - fq result; - result = a + b; - EXPECT_EQ((result == fq::zero()), true); -} - // ================================ // Endomorphism // ================================ @@ -334,78 +186,10 @@ TEST(BN254Fq, SplitIntoEndomorphismEdgeCase) } } -// ================================ -// Buffer Serialization -// ================================ - -TEST(BN254Fq, SerializeToBuffer) -{ - std::array buffer; - fq a = { 0x1234567876543210, 0x2345678987654321, 0x3456789a98765432, 0x006789abcba98765 }; - a = a.to_montgomery_form(); - - fq::serialize_to_buffer(a, &buffer[0]); - - EXPECT_EQ(buffer[31], 0x10); - EXPECT_EQ(buffer[30], 0x32); - EXPECT_EQ(buffer[29], 0x54); - EXPECT_EQ(buffer[28], 0x76); - EXPECT_EQ(buffer[27], 0x78); - EXPECT_EQ(buffer[26], 0x56); - EXPECT_EQ(buffer[25], 0x34); - EXPECT_EQ(buffer[24], 0x12); - - EXPECT_EQ(buffer[23], 0x21); - EXPECT_EQ(buffer[22], 0x43); - EXPECT_EQ(buffer[21], 0x65); - EXPECT_EQ(buffer[20], 0x87); - EXPECT_EQ(buffer[19], 0x89); - EXPECT_EQ(buffer[18], 0x67); - EXPECT_EQ(buffer[17], 0x45); - EXPECT_EQ(buffer[16], 0x23); - - EXPECT_EQ(buffer[15], 0x32); - EXPECT_EQ(buffer[14], 0x54); - EXPECT_EQ(buffer[13], 0x76); - EXPECT_EQ(buffer[12], 0x98); - EXPECT_EQ(buffer[11], 0x9a); - EXPECT_EQ(buffer[10], 0x78); - EXPECT_EQ(buffer[9], 0x56); - EXPECT_EQ(buffer[8], 0x34); - - EXPECT_EQ(buffer[7], 0x65); - EXPECT_EQ(buffer[6], 0x87); - EXPECT_EQ(buffer[5], 0xa9); - EXPECT_EQ(buffer[4], 0xcb); - EXPECT_EQ(buffer[3], 0xab); - EXPECT_EQ(buffer[2], 0x89); - EXPECT_EQ(buffer[1], 0x67); - EXPECT_EQ(buffer[0], 0x00); -} - -TEST(BN254Fq, SerializeFromBuffer) -{ - std::array buffer; - fq expected = { 0x1234567876543210, 0x2345678987654321, 0x3456789a98765432, 0x006789abcba98765 }; - - fq::serialize_to_buffer(expected, &buffer[0]); - fq result = fq::serialize_from_buffer(&buffer[0]); - - EXPECT_EQ(result, expected); -} - // ================================ // Regression Tests // ================================ -// TEST to check we don't have 0^0=0 -TEST(BN254Fq, PowRegressionCheck) -{ - fq zero = fq::zero(); - fq one = fq::one(); - EXPECT_EQ(zero.pow(uint256_t(0)), one); -} -// AUDITTODO: should we remove this test? TEST(BN254Fq, SqrRegression) { std::array values = { diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq12.hpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq12.hpp index 0617253cead4..05f8ed6d3a00 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq12.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq12.hpp @@ -1,5 +1,5 @@ // === AUDIT STATUS === -// internal: { status: Planned, auditors: [], commit: } +// internal: { status: Completed, auditors: [Federico], commit: } // external_1: { status: not started, auditors: [], commit: } // external_2: { status: not started, auditors: [], commit: } // ===================== @@ -11,6 +11,28 @@ #include "./fq6.hpp" namespace bb { + +/** + * @brief The twelfth degree extension of the base field of BN254 + * + * @details Fq12 is defined as Fq6[w] / (w^2 - v), where v is the variable added to Fq2 to construct Fq6. We store in + * the struct the coefficients to compute the frobenius morphism (we need powers up to q^3 to compute the final + * exponentiation in the pairing calculation) + * 1. Power q + * \f[ + * (a + bw)^q = a^q + b^q * w^q = a^q + b^q * \xi^{(q-1)/6} * v + * \f] + * 2. Power q^2 + * \f[ + * (a + bw)^{q^2} = a^{q^2} + b^{q^2} * w^{q^2} = a + b * \xi^{(q^2-1)/6} * v + * \f] + * 3. Power q^3 + * \f[ + * (a + bw)^{q^3} = a^{q^3} + b^{q^3} * w^{q^3} = a^q + b^q * \xi^{(q^3-1)/6} * v + * \f] + * + * + */ struct Bn254Fq12Params { #if defined(__SIZEOF_INT128__) && !defined(__wasm__) @@ -47,4 +69,4 @@ struct Bn254Fq12Params { }; using fq12 = field12; -} // namespace bb \ No newline at end of file +} // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq12.test.cpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq12.test.cpp index d9e558271001..6324124463d7 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq12.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq12.test.cpp @@ -3,102 +3,6 @@ using namespace bb; -TEST(fq12, Eq) -{ - fq12 a = { { { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } } }, - { { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } } } }; - fq12 b = { { { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } } }, - { { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } } } }; - fq12 c = { { { { 0x01, 0x02, 0x03, 0x05 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x05 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x05 }, { 0x06, 0x07, 0x08, 0x09 } } }, - { { { 0x01, 0x02, 0x03, 0x05 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x05 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x05 }, { 0x06, 0x07, 0x08, 0x09 } } } }; - fq12 d = { { { { 0x01, 0x02, 0x04, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x04, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x04, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } } }, - { { { 0x01, 0x02, 0x04, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x04, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x04, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } } } }; - fq12 e = { { { { 0x01, 0x03, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x03, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x03, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } } }, - { { { 0x01, 0x03, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x03, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x03, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } } } }; - fq12 f = { { { { 0x02, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x02, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x02, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } } }, - { { { 0x02, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x02, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x02, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } } } }; - fq12 g = { { { { 0x01, 0x02, 0x03, 0x04 }, { 0x07, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x07, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x07, 0x07, 0x08, 0x09 } } }, - { { { 0x01, 0x02, 0x03, 0x04 }, { 0x07, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x07, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x07, 0x07, 0x08, 0x09 } } } }; - fq12 h = { { { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x08, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x08, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x08, 0x08, 0x09 } } }, - { { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x08, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x08, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x08, 0x08, 0x09 } } } }; - fq12 i = { { { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x09, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x09, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x09, 0x09 } } }, - { { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x09, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x09, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x09, 0x09 } } } }; - fq12 j = { { { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x0a } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x0a } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x0a } } }, - { { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x0a } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x0a } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x0a } } } }; - - EXPECT_EQ((a == b), true); - EXPECT_EQ((a == c), false); - EXPECT_EQ((a == d), false); - EXPECT_EQ((a == e), false); - EXPECT_EQ((a == f), false); - EXPECT_EQ((a == g), false); - EXPECT_EQ((a == h), false); - EXPECT_EQ((a == i), false); - EXPECT_EQ((a == j), false); -} - -TEST(fq12, IsZero) -{ - fq12 a = fq12::zero(); - fq12 b = fq12::zero(); - fq12 c = fq12::zero(); - b.c0.c0.c0.data[0] = 1; - c.c1.c0.c0.data[0] = 1; - EXPECT_EQ(a.is_zero(), true); - EXPECT_EQ(b.is_zero(), false); - EXPECT_EQ(c.is_zero(), false); -} - -TEST(fq12, RandomElement) -{ - fq12 a = fq12::random_element(); - fq12 b = fq12::random_element(); - - EXPECT_EQ(a == b, false); - EXPECT_EQ(a.is_zero(), false); - EXPECT_EQ(a.is_zero(), false); -} - TEST(fq12, AddCheckAgainstConstants) { fq12 a = { { { { 0xe5090b4f4ae647a8, 0xf5d4801f152fdf6c, 0xcdb69d33dba7f562, 0x228f26abab7d6687 }, @@ -403,22 +307,6 @@ TEST(fq12, SqrCheckAgainstConstants) EXPECT_EQ(result, expected); } -TEST(fq12, Inverse) -{ - fq12 input = fq12::random_element(); - fq12 result = input.invert() * input; - EXPECT_EQ(result, fq12::one()); -} - -TEST(fq12, UnitaryInverse) -{ - fq12 input = fq12::random_element(); - fq12 result = input.unitary_inverse(); - EXPECT_EQ(input.c0, result.c0); - result.c1 += input.c1; - EXPECT_EQ(result.c1, fq6::zero()); -} - TEST(fq12, FrobeniusMapThree) { #if defined(__SIZEOF_INT128__) && !defined(__wasm__) @@ -599,66 +487,44 @@ TEST(fq12, FrobeniusMapOne) EXPECT_EQ(result, expected); } -TEST(fq12, ToMontgomeryForm) -{ - fq12 result = fq12::zero(); - result.c0.c0.c0.data[0] = 1; - fq12 expected = fq12::one(); - result = result.to_montgomery_form(); - EXPECT_EQ(result, expected); -} - -TEST(fq12, FromMontgomeryForm) -{ - fq12 result = fq12::one(); - fq12 expected = fq12::zero(); - expected.c0.c0.c0.data[0] = 1; - result = result.from_montgomery_form(); - EXPECT_EQ(result, expected); -} - -TEST(fq12, MulSqrConsistency) +TEST(fq12, FrobeniusMapSixIsConjugation) { fq12 a = fq12::random_element(); - fq12 b = fq12::random_element(); - fq12 t1 = a - b; - fq12 t2 = a + b; - fq12 mul_result = t1 * t2; - fq12 sqr_result = a.sqr() - b.sqr(); - EXPECT_EQ(mul_result, sqr_result); + fq12 result = a.frobenius_map_three().frobenius_map_three(); + EXPECT_EQ(result.c0, a.c0); + EXPECT_EQ(result.c1, -a.c1); } -TEST(fq12, AddMulConsistency) +TEST(fq12, UnitaryInverse) { - fq12 multiplicand = fq12::zero(); - multiplicand.c0.c0.c0.data[0] = 9; - multiplicand = multiplicand.to_montgomery_form(); - - fq12 a = fq12::random_element(); - fq12 result = a + a; - result += result; - result += result; - result += a; - - fq12 expected = a * multiplicand; - - EXPECT_EQ(result, expected); + // Unitary elements can be obtained by taking a random element and raising it to the power + // (q^12 - 1) / (q^4 - q^2 + 1) = (q^2 + 1) (q^6 - 1) + // Note that q^4 - q^2 + 1 is the cyclotomic polynomial of degree 12 evaluated at q + fq12 unitary_input = fq12::random_element(); + unitary_input = unitary_input.frobenius_map_two() * unitary_input; + unitary_input = unitary_input.frobenius_map_three().frobenius_map_three() * unitary_input.invert(); + fq12 result = unitary_input.unitary_inverse(); + + EXPECT_EQ(unitary_input * result, fq12::one()); + EXPECT_EQ(result.c0, unitary_input.c0); + EXPECT_EQ(result.c1, -unitary_input.c1); } -TEST(fq12, SubMulConsistency) +TEST(fq12, FrobeniusCoefficients) { - fq12 multiplicand = fq12::zero(); - multiplicand.c0.c0.c0.data[0] = 5; - multiplicand = multiplicand.to_montgomery_form(); - - fq12 a = fq12::random_element(); - fq12 result = a + a; - result += result; - result += result; - result -= a; - result -= a; - result -= a; - - fq12 expected = a * multiplicand; - EXPECT_EQ(result, expected); + fq2 frobenius_coeff_1{ Bn254Fq12Params::frobenius_coefficients_1 }; + fq2 frobenius_coeff_2{ Bn254Fq12Params::frobenius_coefficients_2 }; + fq2 frobenius_coeff_3{ Bn254Fq12Params::frobenius_coefficients_3 }; + + // \xi^{(q-1)/6} + fq2 expected_frobenius_coeff_1 = fq2{ 0x09, 0x01 }.pow((fq::modulus - 1) / 6); + // \xi^{(q^2-1)/6} = \xi^{(q-1)/6}^{q+1} + fq2 expected_frobenius_coeff_2 = expected_frobenius_coeff_1.pow(fq::modulus + 1); + // \xi^{(q^3-1)/6} = \xi^{(q-1)/6}^{q^2+q+1} + fq2 expected_frobenius_coeff_3 = expected_frobenius_coeff_1.pow(fq::modulus).pow(fq::modulus) * + expected_frobenius_coeff_1.pow(fq::modulus) * expected_frobenius_coeff_1; + + EXPECT_EQ(frobenius_coeff_1, expected_frobenius_coeff_1); + EXPECT_EQ(frobenius_coeff_2, expected_frobenius_coeff_2); + EXPECT_EQ(frobenius_coeff_3, expected_frobenius_coeff_3); } diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq2.hpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq2.hpp index f6563381f47c..103a77395250 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq2.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq2.hpp @@ -1,5 +1,5 @@ // === AUDIT STATUS === -// internal: { status: Planned, auditors: [], commit: } +// internal: { status: Completed, auditors: [Federico], commit: } // external_1: { status: not started, auditors: [], commit: } // external_2: { status: not started, auditors: [], commit: } // ===================== @@ -10,6 +10,22 @@ #include "./fq.hpp" namespace bb { + +/** + * @brief Quadratic extension of the base field of BN254 + * + * @details The quadratic extension Fq2 is defined as Fq[u] / (u^2 + 1). Fq2 is the base field of + * the twist of BN254, thus points in G2 have coordinates in Fq2. + * + * Inside this struct we define the parameters of the twist: + * - the coefficient twist_b = 3 / (9 + u) defining the equation of the twist: y^2 = x^3 + twist_b. The coefficient is + * derived as b / \xi, where b = 3 is the coefficient of BN254 in short-Weierstrass form, and \xi = 9 + u is not a + * sixth residue in Fq2. + * - the coefficients required to compute the frobenius map on the twisted curve: if \Psi : E' --> E is the isomorphism + * between the twist E' and the base curve E, then the frobenius map on E' is \Psi^{-1} \circ Frobenius \circ \Psi. + * This map is given by (x, y) --> (\xi^{(q-1)/3} * x^q, \xi^{(q-1)/2}} * y^q). We precompute the two powers of \xi + * and store them as frobenius_on_twisted_curve_x and frobenius_on_twisted_curve_y. + */ struct Bn254Fq2Params { #if defined(__SIZEOF_INT128__) && !defined(__wasm__) static constexpr fq twist_coeff_b_0{ @@ -18,24 +34,18 @@ struct Bn254Fq2Params { static constexpr fq twist_coeff_b_1{ 0x38e7ecccd1dcff67UL, 0x65f0b37d93ce0d3eUL, 0xd749d0dd22ac00aaUL, 0x0141b9ce4a688d4dUL }; - static constexpr fq twist_mul_by_q_x_0{ + static constexpr fq frobenius_on_twisted_curve_x_0{ 0xb5773b104563ab30UL, 0x347f91c8a9aa6454UL, 0x7a007127242e0991UL, 0x1956bcd8118214ecUL }; - static constexpr fq twist_mul_by_q_x_1{ + static constexpr fq frobenius_on_twisted_curve_x_1{ 0x6e849f1ea0aa4757UL, 0xaa1c7b6d89f89141UL, 0xb6e713cdfae0ca3aUL, 0x26694fbb4e82ebc3UL }; - static constexpr fq twist_mul_by_q_y_0{ + static constexpr fq frobenius_on_twisted_curve_y_0{ 0xe4bbdd0c2936b629UL, 0xbb30f162e133bacbUL, 0x31a9d1b6f9645366UL, 0x253570bea500f8ddUL }; - static constexpr fq twist_mul_by_q_y_1{ + static constexpr fq frobenius_on_twisted_curve_y_1{ 0xa1d77ce45ffe77c7UL, 0x07affd117826d1dbUL, 0x6d16bd27bb7edc6bUL, 0x2c87200285defeccUL }; - static constexpr fq twist_cube_root_0{ - 0x505ecc6f0dff1ac2UL, 0x2071416db35ec465UL, 0xf2b53469fa43ea78UL, 0x18545552044c99aaUL - }; - static constexpr fq twist_cube_root_1{ - 0xad607f911cfe17a8UL, 0xb6bb78aa154154c4UL, 0xb53dd351736b20dbUL, 0x1d8ed57c5cc33d41UL - }; #else static constexpr fq twist_coeff_b_0{ 0xdc19fa4aab489658UL, 0xd416744fbbf6e69UL, 0x8f7734ed0a8a033aUL, 0x19316b8353ee09bbUL @@ -43,26 +53,20 @@ struct Bn254Fq2Params { static constexpr fq twist_coeff_b_1{ 0x1cfd999a3b9fece0UL, 0xbe166fb279c1a7c7UL, 0xe93a1ba45580154cUL, 0x283739c94d11a9baUL }; - static constexpr fq twist_mul_by_q_x_0{ + static constexpr fq frobenius_on_twisted_curve_x_0{ 0xecdea09b24a59190UL, 0x17db8ffeae2fe1c2UL, 0xbb09c97c6dabac4dUL, 0x2492b3d41d289af3UL }; - static constexpr fq twist_mul_by_q_x_1{ + static constexpr fq frobenius_on_twisted_curve_x_1{ 0xf1663598f1142ef1UL, 0x77ec057e0bf56062UL, 0xdd0baaecb677a631UL, 0x135e4e31d284d463UL }; - static constexpr fq twist_mul_by_q_y_0{ + static constexpr fq frobenius_on_twisted_curve_y_0{ 0xf46e7f60db1f0678UL, 0x31fc2eba5bcc5c3eUL, 0xedb3adc3086a2411UL, 0x1d46bd0f837817bcUL }; - static constexpr fq twist_mul_by_q_y_1{ + static constexpr fq frobenius_on_twisted_curve_y_1{ 0x6b3fbdf579a647d5UL, 0xcc568fb62ff64974UL, 0xc1bfbf4ac4348ac6UL, 0x15871d4d3940b4d3UL }; - static constexpr fq twist_cube_root_0{ - 0x49d0cc74381383d0UL, 0x9611849fe4bbe3d6UL, 0xd1a231d73067c92aUL, 0x445c312767932c2UL - }; - static constexpr fq twist_cube_root_1{ - 0x35a58c718e7c28bbUL, 0x98d42c77e7b8901aUL, 0xf9c53da2d0ca8c84UL, 0x1a68dd04e1b8c51dUL - }; #endif }; using fq2 = field2; -} // namespace bb \ No newline at end of file +} // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq2.test.cpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq2.test.cpp index 0a0223b938d7..b7a2ec660032 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq2.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq2.test.cpp @@ -1,54 +1,16 @@ +/** + * @brief BN254 quadratic extension of the base field (fq2) specific tests. + * + * Other field arithmetic tests (both compile-time and runtime) are in ecc/fields/generic_field.test.cpp and + * ecc/fields/prime_field.test.cpp. This file contains only BN254-specific functionality: + * - Fixed tests with field-specific expected values + * - Precomputed constants related to the twist of BN254 + */ #include "fq2.hpp" #include using namespace bb; -TEST(fq2, eq) -{ - fq2 a{ { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }; - fq2 b{ { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }; - fq2 c{ { 0x01, 0x02, 0x03, 0x05 }, { 0x06, 0x07, 0x08, 0x09 } }; - fq2 d{ { 0x01, 0x02, 0x04, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }; - fq2 e{ { 0x01, 0x03, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }; - fq2 f{ { 0x02, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }; - fq2 g{ { 0x01, 0x02, 0x03, 0x04 }, { 0x07, 0x07, 0x08, 0x09 } }; - fq2 h{ { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x08, 0x08, 0x09 } }; - fq2 i{ { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x09, 0x09 } }; - fq2 j{ { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x0a } }; - - EXPECT_EQ(a == b, true); - EXPECT_EQ(a == c, false); - EXPECT_EQ(a == d, false); - EXPECT_EQ(a == e, false); - EXPECT_EQ(a == f, false); - EXPECT_EQ(a == g, false); - EXPECT_EQ(a == h, false); - EXPECT_EQ(a == i, false); - EXPECT_EQ(a == j, false); -} - -TEST(fq2, IsZero) -{ - fq2 a = fq2::zero(); - fq2 b = fq2::zero(); - fq2 c = fq2::zero(); - b.c0.data[0] = 1; - c.c1.data[0] = 1; - EXPECT_EQ(a.is_zero(), true); - EXPECT_EQ(b.is_zero(), false); - EXPECT_EQ(c.is_zero(), false); -} - -TEST(fq2, RandomElement) -{ - fq2 a = fq2::random_element(); - fq2 b = fq2::random_element(); - - EXPECT_EQ(a == b, false); - EXPECT_EQ(a.is_zero(), false); - EXPECT_EQ(b.is_zero(), false); -} - TEST(fq2, MulCheckAgainstConstants) { #if defined(__SIZEOF_INT128__) && !defined(__wasm__) @@ -134,93 +96,32 @@ TEST(fq2, SubCheckAgainstConstants) EXPECT_EQ(result, expected); } -TEST(fq2, ToMontgomeryForm) -{ - fq2 result = fq2::zero(); - result.c0.data[0] = 1; - fq2 expected = fq2::one(); - result.self_to_montgomery_form(); - EXPECT_EQ(result, expected); -} - -TEST(fq2, FromMontgomeryForm) +TEST(fq2, XiNotSexticResidue) { - fq2 result = fq2::one(); - fq2 expected = fq2::zero(); - expected.c0.data[0] = 1; - result.self_from_montgomery_form(); - EXPECT_EQ(result, expected); -} + constexpr fq2 xi{ fq(9), fq(1) }; // 9 + u + fq2 pow_xi = xi.pow((fq::modulus - 1) / 6).pow(fq::modulus + 1); // \xi^((q^2-1)/6) should not be 1 + fq2 one = fq2::one(); -TEST(fq2, MulSqrConsistency) -{ - fq2 a = fq2::random_element(); - fq2 b = fq2::random_element(); - fq2 t1; - fq2 t2; - fq2 mul_result; - fq2 sqr_result; - t1 = a - b; - t2 = a + b; - mul_result = t1 * t2; - t1 = a.sqr(); - t2 = b.sqr(); - sqr_result = t1 - t2; - EXPECT_EQ(mul_result, sqr_result); + EXPECT_NE(pow_xi, one); } -TEST(fq2, AddMulConsistency) +TEST(fq2, TwistBCoefficient) { - fq2 multiplicand = { { 0x09, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00 } }; - multiplicand = multiplicand.to_montgomery_form(); - - fq2 a = fq2::random_element(); - fq2 result = a + a; - result += result; - result += result; - result += a; - - fq2 expected = a * multiplicand; - - EXPECT_EQ(result, expected); + constexpr fq2 twist_b{ Bn254Fq2Params::twist_coeff_b_0, Bn254Fq2Params::twist_coeff_b_1 }; + fq2 expected = fq2{ fq(3), fq(0) } * fq2{ fq(9), fq(1) }.invert(); // 3 / (9+u) + EXPECT_EQ(twist_b, expected); } -TEST(fq2, SubMulConsistency) +TEST(fq2, InverseTwistFrobeniusTwistCoefficients) { - fq2 multiplicand = { { 0x05, 0, 0, 0 }, { 0x00, 0x00, 0x00, 0x00 } }; - multiplicand = multiplicand.to_montgomery_form(); - - fq2 a = fq2::random_element(); - fq2 result = a + a; - result += result; - result += result; - result -= a; - result -= a; - result -= a; + constexpr fq2 xi_q_minus_one_div_3{ Bn254Fq2Params::frobenius_on_twisted_curve_x_0, + Bn254Fq2Params::frobenius_on_twisted_curve_x_1 }; + constexpr fq2 xi_q_minus_one_div_2{ Bn254Fq2Params::frobenius_on_twisted_curve_y_0, + Bn254Fq2Params::frobenius_on_twisted_curve_y_1 }; - fq2 expected = a * multiplicand; - - EXPECT_EQ(result, expected); -} + fq2 expected_x = fq2{ fq(9), fq(1) }.pow((fq::modulus - 1) / 3); // \xi^((q-1)/3) + fq2 expected_y = fq2{ fq(9), fq(1) }.pow((fq::modulus - 1) / 2); // \xi^((q-1)/2) -TEST(fq2, Invert) -{ - fq2 input = fq2::random_element(); - fq2 inverse = input.invert(); - fq2 result = inverse * input; - EXPECT_EQ(result, fq2::one()); + EXPECT_EQ(xi_q_minus_one_div_3, expected_x); + EXPECT_EQ(xi_q_minus_one_div_2, expected_y); } - -TEST(fq2, Serialize) -{ - std::array buffer; - fq expected_c0 = { 0x1234567876543210, 0x2345678987654321, 0x3456789a98765432, 0x006789abcba98765 }; - fq expected_c1 = { 0x12a4e67f76b43210, 0x23e56f898a65cc21, 0x005678add98e5432, 0x1f6789a2cba98700 }; - fq2 expected{ expected_c0, expected_c1 }; - - fq2::serialize_to_buffer(expected, &buffer[0]); - - fq2 result = fq2::serialize_from_buffer(&buffer[0]); - - EXPECT_EQ(result, expected); -} \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq6.hpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq6.hpp index e4abf038884b..fc22af2fa6f7 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq6.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq6.hpp @@ -1,5 +1,5 @@ // === AUDIT STATUS === -// internal: { status: Planned, auditors: [], commit: } +// internal: { status: Completed, auditors: [Federico], commit: } // external_1: { status: not started, auditors: [], commit: } // external_2: { status: not started, auditors: [], commit: } // ===================== @@ -11,6 +11,29 @@ #include "./fq2.hpp" namespace bb { + +/** + * @brief Sextic extension of the base field of BN254 + * + * @details Fq6 is defined as Fq2[v] / (v^3 - \xi), where \xi = 9 + u is not a cubic residue in Fq2. We store in the + * struct the coefficients to compute the frobenius morphism (we need powers up to q^3 to compute the final + * exponentiation in the pairing calculation) + * 1. Power q + * \f[ + * (a + bv + cv^2)^q = a^q + b^q * v^q + c^q * v^{2q} = a^q + b^q * \xi^{(q-1)/3} * v + c^q * \xi^{2(q-1)/3} * v^2 + * \f] + * 2. Power q^2 + * \f[ + * (a + bv + cv^2)^{q^2} = a^{q^2} + b^{q^2} * v^{q^2} + c^{q^2} * v^{2q^2} = + * a + b * \xi^{(q^2-1)/3} * v + c * \xi^{2(q^2-1)/3} * v^2 + * \f] + * 3. Power q^3 + * \f[ + * (a + bv + cv^2)^{q^3} = a^{q^3} + b^{q^3} * v^{q^3} + c^{q^3} * v^{2q^3} = + * a^q + b^q * \xi^{(q^3-1)/3} * v + c^q * \xi^{2(q^3-1)/3} * v^2 + * \f] + * + */ struct Bn254Fq6Params { #if defined(__SIZEOF_INT128__) && !defined(__wasm__) @@ -74,26 +97,26 @@ struct Bn254Fq6Params { }; #endif - // non residue = 9 + i \in Fq2 static inline constexpr fq2 mul_by_non_residue(const fq2& a) { - // non residue = 9 + i \in Fq2 - // r.c0 = 9a0 - a1 - // r.c1 = 9a1 + a0 + // non_residue = 9 + u + // (a + bu) * (9 + u) = (9a - b) + (9b + a)u + + // 9a fq T0 = a.c0 + a.c0; T0 += T0; T0 += T0; T0 += a.c0; + + // 9b fq T1 = a.c1 + a.c1; T1 += T1; T1 += T1; T1 += a.c1; - fq T2 = T0 - a.c1; - return { T2, T1 + a.c0 }; - T0 = a.c0 + a.c0; + return { T0 - a.c1, T1 + a.c0 }; } }; using fq6 = field6; -} // namespace bb \ No newline at end of file +} // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq6.test.cpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq6.test.cpp index cc0c7e45abb5..ca7026eca55f 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq6.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq6.test.cpp @@ -3,75 +3,6 @@ using namespace bb; -TEST(fq6, Eq) -{ - fq6 a{ { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } } }; - fq6 b{ { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } } }; - fq6 c{ { { 0x01, 0x02, 0x03, 0x05 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x05 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x05 }, { 0x06, 0x07, 0x08, 0x09 } } }; - fq6 d{ { { 0x01, 0x02, 0x04, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x04, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x04, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } } }; - fq6 e{ { { 0x01, 0x03, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x03, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x03, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } } }; - fq6 f{ { { 0x02, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x02, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x02, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } } }; - fq6 g{ { { 0x01, 0x02, 0x03, 0x04 }, { 0x07, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x07, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x07, 0x07, 0x08, 0x09 } } }; - fq6 h{ { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x08, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x08, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x08, 0x08, 0x09 } } }; - fq6 i{ { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x09, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x09, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x09, 0x09 } } }; - fq6 j{ { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x0a } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x0a } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x0a } } }; - - EXPECT_EQ((a == b), true); - EXPECT_EQ((a == c), false); - EXPECT_EQ((a == d), false); - EXPECT_EQ((a == e), false); - EXPECT_EQ((a == f), false); - EXPECT_EQ((a == g), false); - EXPECT_EQ((a == h), false); - EXPECT_EQ((a == i), false); - EXPECT_EQ((a == j), false); -} - -TEST(fq6, IsZero) -{ - fq6 a = fq6::zero(); - fq6 b = fq6::zero(); - fq6 c = fq6::zero(); - fq6 d = fq6::zero(); - b.c0.c0.data[0] = 1; - c.c1.c0.data[0] = 1; - d.c2.c0.data[0] = 1; - EXPECT_EQ(a.is_zero(), true); - EXPECT_EQ(b.is_zero(), false); - EXPECT_EQ(c.is_zero(), false); - EXPECT_EQ(d.is_zero(), false); -} - -TEST(fq6, RandomElement) -{ - fq6 a = fq6::random_element(); - fq6 b = fq6::random_element(); - - EXPECT_EQ((a == b), false); - EXPECT_EQ(a.is_zero(), false); - EXPECT_EQ(b.is_zero(), false); -} - TEST(fq6, AddCheckAgainstConstants) { fq6 a{ { { 0x68138b3c3e5e820b, 0x9bf71d36786da85f, 0x815831c12e257996, 0x2280b875a27e6d1d }, @@ -169,7 +100,6 @@ TEST(fq6, MulCheckAgainstConstants) TEST(fq6, SqrCheckAgainstConstants) { - #if defined(__SIZEOF_INT128__) && !defined(__wasm__) fq6 a{ { { 0xe337aaa063afce6, 0xff4b5477485eb20, 0xef6dcf13b3855ef8, 0x14554c38da988ece }, { 0x6a70e65e71431416, 0xd21f95045c45f422, 0x2a17b6c6ff517884, 0x1b01ad6487a3ff16 } }, @@ -202,84 +132,43 @@ TEST(fq6, SqrCheckAgainstConstants) EXPECT_EQ(result, expected); } -TEST(fq6, ToMontgomeryForm) -{ - fq6 result = fq6::zero(); - result.c0.c0.data[0] = 1; - fq6 expected = fq6::one(); - result = result.to_montgomery_form(); - EXPECT_EQ(result, expected); -} - -TEST(fq6, FromMontgomeryForm) -{ - fq6 result = fq6::one(); - fq6 expected = fq6::zero(); - expected.c0.c0.data[0] = 1; - result = result.from_montgomery_form(); - EXPECT_EQ(result, expected); -} - -TEST(fq6, MulSqrConsistency) +TEST(fq6, FrobeniusCoefficients) { - fq6 a = fq6::random_element(); - fq6 b = fq6::random_element(); - fq6 t1 = a - b; - fq6 t2 = a + b; - fq6 mul_result = t1 * t2; - fq6 sqr_result = a.sqr() - b.sqr(); - - EXPECT_EQ(mul_result, sqr_result); -} - -TEST(fq6, AddMulConsistency) -{ - fq6 multiplicand = fq6::zero(); - multiplicand.c0.c0.data[0] = 9; - multiplicand = multiplicand.to_montgomery_form(); - - fq6 a = fq6::random_element(); - fq6 result = a + a; - result += result; - result += result; - result += a; - - fq6 expected = a * multiplicand; - EXPECT_EQ(result, expected); + fq2 frobenius_coeff_1_1{ Bn254Fq6Params::frobenius_coeffs_c1_1 }; + fq2 frobenius_coeff_1_2{ Bn254Fq6Params::frobenius_coeffs_c1_2 }; + fq2 frobenius_coeff_1_3{ Bn254Fq6Params::frobenius_coeffs_c1_3 }; + fq2 frobenius_coeff_2_1{ Bn254Fq6Params::frobenius_coeffs_c2_1 }; + fq2 frobenius_coeff_2_2{ Bn254Fq6Params::frobenius_coeffs_c2_2 }; + fq2 frobenius_coeff_2_3{ Bn254Fq6Params::frobenius_coeffs_c2_3 }; + + // \xi^{(q-1)/3} + fq2 expected_frobenius_coeff_1_1 = fq2{ 0x09, 0x01 }.pow((fq::modulus - 1) / 3); + // \xi^{(q^2-1)/3} = \xi^{(q-1)/3}^{q+1} + fq2 expected_frobenius_coeff_1_2 = expected_frobenius_coeff_1_1.pow(fq::modulus + 1); + // \xi^{(q^3-1)/3} = \xi^{(q-1)/3}^{q^2+q+1} + fq2 expected_frobenius_coeff_1_3 = expected_frobenius_coeff_1_1.pow(fq::modulus).pow(fq::modulus) * + expected_frobenius_coeff_1_1.pow(fq::modulus) * expected_frobenius_coeff_1_1; + // \xi^{2(q-1)/3} + fq2 expected_frobenius_coeff_2_1 = fq2{ 0x09, 0x01 }.pow(uint256_t(2) * (fq::modulus - 1) / 3); + // \xi^{2(q^2-1)/3} = \xi^{2(q-1)/3}^{q+1} + fq2 expected_frobenius_coeff_2_2 = expected_frobenius_coeff_2_1.pow(fq::modulus + 1); + // \xi^{2(q^3-1)/3} = \xi^{2(q-1)/3}^{q^2+q+1} + fq2 expected_frobenius_coeff_2_3 = expected_frobenius_coeff_2_1.pow(fq::modulus).pow(fq::modulus) * + expected_frobenius_coeff_2_1.pow(fq::modulus) * expected_frobenius_coeff_2_1; + + EXPECT_EQ(frobenius_coeff_1_1, expected_frobenius_coeff_1_1); + EXPECT_EQ(frobenius_coeff_1_2, expected_frobenius_coeff_1_2); + EXPECT_EQ(frobenius_coeff_1_3, expected_frobenius_coeff_1_3); + EXPECT_EQ(frobenius_coeff_2_1, expected_frobenius_coeff_2_1); + EXPECT_EQ(frobenius_coeff_2_2, expected_frobenius_coeff_2_2); + EXPECT_EQ(frobenius_coeff_2_3, expected_frobenius_coeff_2_3); } -TEST(fq6, SubMulConsistency) +TEST(fq6, MulByNonResidue) { - fq6 multiplicand = fq6::zero(); - multiplicand.c0.c0.data[0] = 5; - multiplicand = multiplicand.to_montgomery_form(); - fq6 a = fq6::random_element(); - fq6 result = a + a; - result += result; - result += result; - result -= a; - result -= a; - result -= a; - - fq6 expected = a * multiplicand; + fq2 one = fq2::one(); + fq2 result = fq6::mul_by_non_residue(one); + fq2 expected = fq2{ 0x09, 0x01 }; EXPECT_EQ(result, expected); } - -TEST(fq6, Invert) -{ - fq6 input = fq6::random_element(); - fq6 result = input.invert(); - - result *= input; - EXPECT_EQ(result, fq6::one()); -} - -TEST(fq6, Copy) -{ - fq6 result = fq6::random_element(); - - // NOLINTNEXTLINE(performance-unnecessary-copy-initialization) this is what we want to test! - fq6 expected = result; - EXPECT_EQ(result, expected); -} \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fr.hpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fr.hpp index 3f39b6541d24..b9acfd21a730 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fr.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fr.hpp @@ -1,5 +1,5 @@ // === AUDIT STATUS === -// internal: { status: Planned, auditors: [], commit: } +// internal: { status: Completed, auditors: [Federico], commit: } // external_1: { status: not started, auditors: [], commit: } // external_2: { status: not started, auditors: [], commit: } // ===================== @@ -17,13 +17,17 @@ namespace bb { +/** + * @brief Parameters defining the scalar field of the BN254 curve. + * + * @details When split into 4 64-bit words, the parameters are represented in little-endian, i.e. the least significant + * bit comes first. For example, to recover the modulus from the 64-bit words we concatenate its limbs to obtain: + * 0x30644E72E131A029B85045B68181585D2833E84879B9709143E1F593F0000001 + * + * @note These parameters can be extracted by running the script parameter_helper.py in ecc/fields + */ class Bn254FrParams { - // There is a helper script in ecc/fields/parameter_helper.py that can be used to extract these parameters from the public: - // Note: limbs here are combined as concat(_3, _2, _1, _0) - // E.g. this modulus forms the value: - // 0x30644E72E131A029B85045B68181585D2833E84879B9709143E1F593F0000001 - // = 21888242871839275222246405745257275088548364400416034343698204186575808495617 // A little-endian representation of the modulus split into 4 64-bit words static constexpr uint64_t modulus_0 = 0x43E1F593F0000001UL; static constexpr uint64_t modulus_1 = 0x2833E84879B97091UL; @@ -36,39 +40,15 @@ class Bn254FrParams { static constexpr uint64_t r_squared_2 = 0x8C49833D53BB8085UL; static constexpr uint64_t r_squared_3 = 0x216D0B17F4E44A5UL; - // A little-endian representation of the cubic root of 1 in Fr in Montgomery form split into 4 64-bit words - static constexpr uint64_t cube_root_0 = 0x93e7cede4a0329b3UL; - static constexpr uint64_t cube_root_1 = 0x7d4fdca77a96c167UL; - static constexpr uint64_t cube_root_2 = 0x8be4ba08b19a750aUL; - static constexpr uint64_t cube_root_3 = 0x1cbd5653a5661c25UL; - - // A little-endian representation of the primitive root of 1 Fr split into 4 64-bit words in Montgomery form - // (R=2^256 mod modulus) This is a root of unity in a large power of 2 subgroup of Fr - static constexpr uint64_t primitive_root_0 = 0x636e735580d13d9cUL; - static constexpr uint64_t primitive_root_1 = 0xa22bf3742445ffd6UL; - static constexpr uint64_t primitive_root_2 = 0x56452ac01eb203d8UL; - static constexpr uint64_t primitive_root_3 = 0x1860ef942963f9e7UL; - - // Parameters used for quickly splitting a scalar into two endomorphism scalars for faster scalar multiplication - // For specifics on how these have been derived, ask @zac-williamson - static constexpr uint64_t endo_g1_lo = 0x7a7bd9d4391eb18dUL; - static constexpr uint64_t endo_g1_mid = 0x4ccef014a773d2cfUL; - static constexpr uint64_t endo_g1_hi = 0x0000000000000002UL; - static constexpr uint64_t endo_g2_lo = 0xd91d232ec7e0b3d7UL; - static constexpr uint64_t endo_g2_mid = 0x0000000000000002UL; - static constexpr uint64_t endo_minus_b1_lo = 0x8211bbeb7d4f1128UL; - static constexpr uint64_t endo_minus_b1_mid = 0x6f4d8248eeb859fcUL; - static constexpr uint64_t endo_b2_lo = 0x89d3256894d213e3UL; - static constexpr uint64_t endo_b2_mid = 0UL; - // -(Modulus^-1) mod 2^64 - // This is used to compute k = r_inv * lower_limb(scalar), such that scalar + k*modulus in integers would have 0 in - // the lowest limb By performing this sequentially for 4 limbs, we get an 8-limb representation of the scalar, where - // the lowest 4 limbs are zeros. Then we can immediately divide by 2^256 by simply getting rid of the lowest 4 limbs + // This constant is used during multiplication: given an 8-limb representation of the multiplication of two field + // elements, for each of the lowest four limbs we compute: k_i = r_inv * limb_i and we add 2^{64 * i} * k_i * p to + // the result of the multiplication. In this way we zero out the lowest four limbs of the multiplication and we can + // divide by 2^256 by taking the highest four limbs. See field_docs.hpp for more details. static constexpr uint64_t r_inv = 0xc2e1f593efffffffUL; // 2^(-64) mod Modulus - // Used in the reduction mechanism from https://hackmd.io/@Ingonyama/Barret-Montgomery + // Used in the reduction mechanism, see field_docs.md // Instead of computing k, we multiply the lowest limb by this value and then add to the following 5 limbs. // This saves us from having to compute k static constexpr uint64_t r_inv_0 = 0x2d3e8053e396ee4dUL; @@ -76,37 +56,24 @@ class Bn254FrParams { static constexpr uint64_t r_inv_2 = 0xb2d8f06f77f52a93UL; static constexpr uint64_t r_inv_3 = 0x24d6ba07f7aa8f04UL; - // 2^(-29) mod Modulus - // Used in the reduction mechanism from https://hackmd.io/@Ingonyama/Barret-Montgomery - // Instead of computing k, we multiply the lowest limb by this value and then add to the following 10 limbs. - // This saves us from having to compute k - static constexpr uint64_t r_inv_wasm_0 = 0x18f05361; - static constexpr uint64_t r_inv_wasm_1 = 0x12bb1fe; - static constexpr uint64_t r_inv_wasm_2 = 0xf5d8135; - static constexpr uint64_t r_inv_wasm_3 = 0x1e6275f6; - static constexpr uint64_t r_inv_wasm_4 = 0x7e7a880; - static constexpr uint64_t r_inv_wasm_5 = 0x10c6bf1f; - static constexpr uint64_t r_inv_wasm_6 = 0x11f74a6c; - static constexpr uint64_t r_inv_wasm_7 = 0x6fdaecb; - static constexpr uint64_t r_inv_wasm_8 = 0x183227; + // A little-endian representation of the cubic root of 1 in Fr in Montgomery form split into 4 64-bit words + static constexpr uint64_t cube_root_0 = 0x93e7cede4a0329b3UL; + static constexpr uint64_t cube_root_1 = 0x7d4fdca77a96c167UL; + static constexpr uint64_t cube_root_2 = 0x8be4ba08b19a750aUL; + static constexpr uint64_t cube_root_3 = 0x1cbd5653a5661c25UL; + + // A little-endian representation of the primitive root of 1 in Fr split into 4 64-bit words in Montgomery form + // (R=2^256 mod modulus). This is a root of unity in a large power of 2 (order 28) subgroup of Fr. + static constexpr uint64_t primitive_root_0 = 0x636e735580d13d9cUL; + static constexpr uint64_t primitive_root_1 = 0xa22bf3742445ffd6UL; + static constexpr uint64_t primitive_root_2 = 0x56452ac01eb203d8UL; + static constexpr uint64_t primitive_root_3 = 0x1860ef942963f9e7UL; // Coset generators in Montgomery form for R=2^256 mod Modulus. Used in FFT-based proving systems - static constexpr uint64_t coset_generators_0[8]{ - 0x5eef048d8fffffe7ULL, 0xb8538a9dfffffe2ULL, 0x3057819e4fffffdbULL, 0xdcedb5ba9fffffd6ULL, - 0x8983e9d6efffffd1ULL, 0x361a1df33fffffccULL, 0xe2b0520f8fffffc7ULL, 0x8f46862bdfffffc2ULL, - }; - static constexpr uint64_t coset_generators_1[8]{ - 0x12ee50ec1ce401d0ULL, 0x49eac781bc44cefaULL, 0x307f6d866832bb01ULL, 0x677be41c0793882aULL, - 0x9e785ab1a6f45554ULL, 0xd574d1474655227eULL, 0xc7147dce5b5efa7ULL, 0x436dbe728516bcd1ULL, - }; - static constexpr uint64_t coset_generators_2[8]{ - 0x29312d5a5e5ee7ULL, 0x6697d49cd2d7a515ULL, 0x5c65ec9f484e3a89ULL, 0xc2d4900ec0c780b7ULL, - 0x2943337e3940c6e5ULL, 0x8fb1d6edb1ba0d13ULL, 0xf6207a5d2a335342ULL, 0x5c8f1dcca2ac9970ULL, - }; - static constexpr uint64_t coset_generators_3[8]{ - 0x463456c802275bedULL, 0x543ece899c2f3b1cULL, 0x180a96573d3d9f8ULL, 0xf8b21270ddbb927ULL, - 0x1d9598e8a7e39857ULL, 0x2ba010aa41eb7786ULL, 0x39aa886bdbf356b5ULL, 0x47b5002d75fb35e5ULL, - }; + static constexpr uint64_t coset_generator_0 = 0x5eef048d8fffffe7ULL; + static constexpr uint64_t coset_generator_1 = 0x12ee50ec1ce401d0ULL; + static constexpr uint64_t coset_generator_2 = 0x29312d5a5e5ee7ULL; + static constexpr uint64_t coset_generator_3 = 0x463456c802275bedULL; // A little-endian representation of the modulus split into 9 29-bit limbs // This is used in wasm because we can only do multiplication with 64-bit result instead of 128-bit like in x86_64 @@ -121,12 +88,26 @@ class Bn254FrParams { static constexpr uint64_t modulus_wasm_8 = 0x30644e; // A little-endian representation of R^2 modulo the modulus (R=2^261 mod modulus) split into 4 64-bit words - // We use 2^261 in wasm, because 261=29*9, the 9 29-bit limbs used for arithmetic in + // We use 2^261 in wasm, because 261=29*9, the 9 29-bit limbs used for arithmetic static constexpr uint64_t r_squared_wasm_0 = 0x38c2e14b45b69bd4UL; static constexpr uint64_t r_squared_wasm_1 = 0x0ffedb1885883377UL; static constexpr uint64_t r_squared_wasm_2 = 0x7840f9f0abc6e54dUL; static constexpr uint64_t r_squared_wasm_3 = 0x0a054a3e848b0f05UL; + // 2^(-29) mod Modulus + // Used in the reduction mechanism, see field_docs.md + // Instead of computing k, we multiply the lowest limb by this value and then add to the following 10 limbs. + // This saves us from having to compute k + static constexpr uint64_t r_inv_wasm_0 = 0x18f05361; + static constexpr uint64_t r_inv_wasm_1 = 0x12bb1fe; + static constexpr uint64_t r_inv_wasm_2 = 0xf5d8135; + static constexpr uint64_t r_inv_wasm_3 = 0x1e6275f6; + static constexpr uint64_t r_inv_wasm_4 = 0x7e7a880; + static constexpr uint64_t r_inv_wasm_5 = 0x10c6bf1f; + static constexpr uint64_t r_inv_wasm_6 = 0x11f74a6c; + static constexpr uint64_t r_inv_wasm_7 = 0x6fdaecb; + static constexpr uint64_t r_inv_wasm_8 = 0x183227; + // A little-endian representation of the cubic root of 1 in Fr in Montgomery form for wasm (R=2^261 mod modulus) // split into 4 64-bit words static constexpr uint64_t cube_root_wasm_0 = 0x7334a1ce7065364dUL; @@ -142,22 +123,22 @@ class Bn254FrParams { static constexpr uint64_t primitive_root_wasm_3 = 0x05d90b5719653a4fUL; // Coset generators in Montgomery form for R=2^261 mod Modulus. Used in FFT-based proving systems - static constexpr uint64_t coset_generators_wasm_0[8] = { 0xab46711cdffffcb2ULL, 0xdb1b52736ffffc09ULL, - 0x0af033c9fffffb60ULL, 0xf6e31f8c9ffffab6ULL, - 0x26b800e32ffffa0dULL, 0x568ce239bffff964ULL, - 0x427fcdfc5ffff8baULL, 0x7254af52effff811ULL }; - static constexpr uint64_t coset_generators_wasm_1[8] = { 0x2476607dbd2dfff1ULL, 0x9a3208a561c2b00bULL, - 0x0fedb0cd06576026ULL, 0x5d7570ac31329faeULL, - 0xd33118d3d5c74fc9ULL, 0x48ecc0fb7a5bffe3ULL, - 0x967480daa5373f6cULL, 0x0c30290249cbef86ULL }; - static constexpr uint64_t coset_generators_wasm_2[8] = { 0xe6b99ee0068dfc25ULL, 0x39bb9964882aa6a5ULL, - 0x8cbd93e909c75126ULL, 0x276f48b709e2a349ULL, - 0x7a71433b8b7f4dc9ULL, 0xcd733dc00d1bf84aULL, - 0x6824f28e0d374a6dULL, 0xbb26ed128ed3f4eeULL }; - static constexpr uint64_t coset_generators_wasm_3[8] = { 0x1484c05bce00b620ULL, 0x224cf685243dfa96ULL, - 0x30152cae7a7b3f0bULL, 0x0d791464ef86e357ULL, - 0x1b414a8e45c427ccULL, 0x290980b79c016c41ULL, - 0x066d686e110d108dULL, 0x14359e97674a5502ULL }; + static constexpr uint64_t coset_generator_wasm_0 = 0xab46711cdffffcb2ULL; + static constexpr uint64_t coset_generator_wasm_1 = 0x2476607dbd2dfff1ULL; + static constexpr uint64_t coset_generator_wasm_2 = 0xe6b99ee0068dfc25ULL; + static constexpr uint64_t coset_generator_wasm_3 = 0x1484c05bce00b620ULL; + + // Parameters used for quickly splitting a scalar into two endomorphism scalars for faster scalar multiplication + // For specifics on how these have been derived, see ecc/fields/endomorphim_scalars.py + static constexpr uint64_t endo_g1_lo = 0x7a7bd9d4391eb18dUL; + static constexpr uint64_t endo_g1_mid = 0x4ccef014a773d2cfUL; + static constexpr uint64_t endo_g1_hi = 0x0000000000000002UL; + static constexpr uint64_t endo_g2_lo = 0xd91d232ec7e0b3d7UL; + static constexpr uint64_t endo_g2_mid = 0x0000000000000002UL; + static constexpr uint64_t endo_minus_b1_lo = 0x8211bbeb7d4f1128UL; + static constexpr uint64_t endo_minus_b1_mid = 0x6f4d8248eeb859fcUL; + static constexpr uint64_t endo_b2_lo = 0x89d3256894d213e3UL; + static constexpr uint64_t endo_b2_mid = 0UL; // used in msgpack schema serialization static constexpr char schema_name[] = "fr"; diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fr.test.cpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fr.test.cpp index 6defeae8da9e..527d8ff82501 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fr.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fr.test.cpp @@ -89,6 +89,9 @@ TEST(BN254Fr, SplitIntoEndomorphismScalars) k1.self_to_montgomery_form(); k2.self_to_montgomery_form(); + EXPECT_LT(uint256_t(k1).get_msb(), 128); + EXPECT_LT(uint256_t(k2).get_msb(), 128); + fr lambda = fr::cube_root_of_unity(); result = k2 * lambda; result = k1 - result; @@ -106,11 +109,13 @@ TEST(BN254Fr, SplitIntoEndomorphismScalarsSimple) fr::__copy(input, k); fr::split_into_endomorphism_scalars(k, k1, k2); - // AUDITTODO: double check this test. fr result{ 0, 0, 0, 0 }; k1.self_to_montgomery_form_reduced(); k2.self_to_montgomery_form_reduced(); + EXPECT_LT(uint256_t(k1).get_msb(), 128); + EXPECT_LT(uint256_t(k2).get_msb(), 128); + fr lambda = fr::cube_root_of_unity(); result = k2 * lambda; result = k1 - result; diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/g1.hpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/g1.hpp index f30f2f94abb8..c6e5ea4afc63 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/g1.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/g1.hpp @@ -16,6 +16,8 @@ struct Bn254G1Params { static constexpr bool can_hash_to_curve = true; static constexpr bool small_elements = true; static constexpr bool has_a = false; + + // Generator = (1, sqrt(4)) = (1, 2) static constexpr fq one_x = fq::one(); #if defined(__SIZEOF_INT128__) && !defined(__wasm__) static constexpr fq one_y{ 0xa6ba871b8b1e1b3aUL, 0x14f1d651eb8e167bUL, 0xccdd46def0f28c58UL, 0x1c14ef83340fbe5eUL }; diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/g1.test.cpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/g1.test.cpp index ea3651b8a83c..8b6066ee8535 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/g1.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/g1.test.cpp @@ -23,6 +23,30 @@ typename Group::affine_element naive_scalar_mul(const typename Group::element& b } } // namespace +// ========================= +// Parameter-related tests +// ========================= + +TEST(g1, BIsCorrect) +{ + fq b = Bn254G1Params::b; + fq expected = fq(3); + + EXPECT_EQ(b, expected); +} + +TEST(g1, OneYIsCorrect) +{ + fq one_y = Bn254G1Params::one_y; + auto [_, expected] = (Bn254G1Params::b + fq::one()).sqrt(); + + EXPECT_EQ(one_y, expected); +} + +// ========================= +// Group-related tests +// ========================= + TEST(g1, RandomElement) { g1::element result = g1::element::random_element(); diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/g2.test.cpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/g2.test.cpp index ce198af48a91..efa574fed661 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/g2.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/g2.test.cpp @@ -387,4 +387,15 @@ TEST(g2, InitializationCheck) // NOLINTNEXTLINE not our fault googletest uses `goto`! EXPECT_NO_THROW(write({})); } -#endif \ No newline at end of file +#endif + +TEST(g2, GeneratorIsCorrect) +{ + // Values taken from https://eips.ethereum.org/EIPS/eip-197 + g2::affine_element generator{ Bn254G2Params::one_x, Bn254G2Params::one_y }; + g2::affine_element expected{ fq2{ fq("0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed"), + fq("0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2") }, + fq2{ fq("0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa"), + fq("0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b") } }; + EXPECT_EQ(generator, expected); +} diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/pairing_impl.hpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/pairing_impl.hpp index 33d889c6dc75..1968365cdbbd 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/pairing_impl.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/pairing_impl.hpp @@ -19,8 +19,8 @@ inline constexpr g2::element mul_by_q(const g2::element& a) fq2 T0 = a.x.frobenius_map(); fq2 T1 = a.y.frobenius_map(); return { - fq2::twist_mul_by_q_x() * T0, - fq2::twist_mul_by_q_y() * T1, + fq2::frobenius_on_twisted_curve_x() * T0, + fq2::frobenius_on_twisted_curve_y() * T1, a.z.frobenius_map(), }; } diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/field_params_constants.test.cpp b/barretenberg/cpp/src/barretenberg/ecc/curves/field_params_constants.test.cpp new file mode 100644 index 000000000000..1433076c7b5c --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/field_params_constants.test.cpp @@ -0,0 +1,353 @@ +/** + * @brief Typed test fixture verifying field parameter constants for all supported curves. + * + * Each field is described by a Config struct that bundles: + * - Params: the field params struct (e.g. Bn254FqParams) + * - Field: the instantiated field type (e.g. bb::fq = field) + * - expected_modulus_decimal: the expected modulus as a decimal string (ground truth reference) + * - has_cube_root: whether a meaningful cube root of unity exists in this field + * - has_primitive_root: whether a high-2-adicity primitive root of unity is used + * + * Tests cover native (64-bit limb) and WASM (29-bit limb) representations of all constants. + * + * Fields tested: + * - BN254: Fq (base field), Fr (scalar field) + * - secp256k1: Fq (base field), Fr (scalar field) + * - secp256r1: Fq (base field), Fr (scalar field) + * + * Note: Grumpkin reuses BN254's fq/fr (swapped), so no separate config is needed. + */ + +#include "barretenberg/ecc/curves/bn254/fq.hpp" +#include "barretenberg/ecc/curves/bn254/fr.hpp" +#include "barretenberg/ecc/curves/secp256k1/secp256k1.hpp" +#include "barretenberg/ecc/curves/secp256r1/secp256r1.hpp" +#include "barretenberg/numeric/random/engine.hpp" +#include "barretenberg/numeric/uint256/uint256.hpp" +#include +#include +#include + +using namespace bb; + +namespace { + +// Helper to convert a decimal string to uint256_t. +uint256_t from_decimal(const std::string& dec_str) +{ + uint256_t result = 0; + for (char c : dec_str) { + result = result * 10 + static_cast(c - '0'); + } + return result; +} + +// ============================================================ +// Config structs — one per field, providing expected constants +// and feature flags that control which tests are exercised. +// ============================================================ + +struct Bn254FqTestConfig { + using Params = Bn254FqParams; + using Field = bb::fq; + // BN254 base field prime q + // References: https://eips.ethereum.org/EIPS/eip-196, https://hackmd.io/@jpw/bn254 + static constexpr const char* expected_modulus_decimal = + "21888242871839275222246405745257275088696311157297823662689037894645226208583"; + static constexpr bool has_cube_root = true; + static constexpr bool has_primitive_root = false; +}; + +struct Bn254FrTestConfig { + using Params = Bn254FrParams; + using Field = bb::fr; + // BN254 scalar field prime r (also Baby Jubjub base field) + // References: https://eips.ethereum.org/EIPS/eip-196, https://hackmd.io/@jpw/bn254 + static constexpr const char* expected_modulus_decimal = + "21888242871839275222246405745257275088548364400416034343698204186575808495617"; + static constexpr bool has_cube_root = true; + static constexpr bool has_primitive_root = true; +}; + +struct Secp256k1FqTestConfig { + using Params = secp256k1::FqParams; + using Field = secp256k1::fq; + // secp256k1 base field prime p = 2^256 - 2^32 - 977 + // Reference: https://www.secg.org/sec2-v2.pdf + static constexpr const char* expected_modulus_decimal = + "115792089237316195423570985008687907853269984665640564039457584007908834671663"; + static constexpr bool has_cube_root = true; + static constexpr bool has_primitive_root = false; +}; + +struct Secp256k1FrTestConfig { + using Params = secp256k1::FrParams; + using Field = secp256k1::fr; + // secp256k1 scalar field order + // Reference: https://www.secg.org/sec2-v2.pdf + static constexpr const char* expected_modulus_decimal = + "115792089237316195423570985008687907852837564279074904382605163141518161494337"; + static constexpr bool has_cube_root = true; + static constexpr bool has_primitive_root = false; +}; + +struct Secp256r1FqTestConfig { + using Params = secp256r1::FqParams; + using Field = secp256r1::fq; + // secp256r1 (P-256) base field prime p = 2^256 - 2^224 + 2^192 + 2^96 - 1 + // Reference: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf + static constexpr const char* expected_modulus_decimal = + "115792089210356248762697446949407573530086143415290314195533631308867097853951"; + static constexpr bool has_cube_root = false; + static constexpr bool has_primitive_root = false; +}; + +struct Secp256r1FrTestConfig { + using Params = secp256r1::FrParams; + using Field = secp256r1::fr; + // secp256r1 (P-256) scalar field order + // Reference: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf + static constexpr const char* expected_modulus_decimal = + "115792089210356248762697446949407573529996955224135760342422259061068512044369"; + static constexpr bool has_cube_root = false; + static constexpr bool has_primitive_root = false; +}; + +} // namespace + +// ============================================================ +// Typed test fixture +// ============================================================ + +template class FieldConstantsTest : public testing::Test {}; + +TYPED_TEST_SUITE_P(FieldConstantsTest); + +// Verify that the 4 x 64-bit limbs reconstruct to the expected modulus. +TYPED_TEST_P(FieldConstantsTest, Modulus) +{ + using Params = typename TypeParam::Params; + uint256_t expected = from_decimal(TypeParam::expected_modulus_decimal); + uint256_t actual{ Params::modulus_0, Params::modulus_1, Params::modulus_2, Params::modulus_3 }; + EXPECT_EQ(expected, actual); +} + +// Verify R^2 mod p, where R = 2^256, stored in r_squared_{0-3}. +TYPED_TEST_P(FieldConstantsTest, RSquared) +{ + using Params = typename TypeParam::Params; + uint256_t mod{ Params::modulus_0, Params::modulus_1, Params::modulus_2, Params::modulus_3 }; + uint512_t R = (uint512_t(1) << 256) % mod; + uint512_t expected = (R * R) % mod; + uint256_t actual{ Params::r_squared_0, Params::r_squared_1, Params::r_squared_2, Params::r_squared_3 }; + EXPECT_EQ(expected.lo, actual); +} + +// Verify r_inv = -(modulus^{-1}) mod 2^64, used for Montgomery reduction. +TYPED_TEST_P(FieldConstantsTest, RInv) +{ + using Params = typename TypeParam::Params; + uint256_t mod{ Params::modulus_0, Params::modulus_1, Params::modulus_2, Params::modulus_3 }; + uint512_t two_64 = uint512_t(1) << 64; + uint512_t neg_mod{ -mod, 0 }; + uint64_t expected = neg_mod.invmod(two_64).lo.data[0]; + EXPECT_EQ(Params::r_inv, expected); +} + +// Verify r_inv_{0-3} = 2^{-64} mod p, used in the Barrett-Montgomery reduction. +TYPED_TEST_P(FieldConstantsTest, PowMinus64) +{ + using Params = typename TypeParam::Params; + uint256_t mod{ Params::modulus_0, Params::modulus_1, Params::modulus_2, Params::modulus_3 }; + uint512_t two_64 = uint512_t(1) << 64; + uint256_t expected = two_64.invmod(mod).lo; + EXPECT_EQ(expected.data[0], Params::r_inv_0); + EXPECT_EQ(expected.data[1], Params::r_inv_1); + EXPECT_EQ(expected.data[2], Params::r_inv_2); + EXPECT_EQ(expected.data[3], Params::r_inv_3); +} + +// Verify that beta = cube_root_of_unity() satisfies beta^3 = 1 and beta != 1. +TYPED_TEST_P(FieldConstantsTest, CubeRootOfUnity) +{ + if constexpr (!TypeParam::has_cube_root) { + GTEST_SKIP() << "Cube root of unity is not defined for this field"; + } else { + using Field = typename TypeParam::Field; + Field beta = Field::cube_root_of_unity(); + EXPECT_EQ(beta * beta * beta, Field::one()); + EXPECT_NE(beta, Field::one()); + } +} + +// Verify that get_root_of_unity(order) is a primitive 2^order root of unity. +TYPED_TEST_P(FieldConstantsTest, PrimitiveRootOfUnity) +{ + if constexpr (!TypeParam::has_primitive_root) { + GTEST_SKIP() << "Primitive root of unity is not used for this field"; + } else { + using Field = typename TypeParam::Field; + size_t order = Field::primitive_root_log_size(); + Field root = Field::get_root_of_unity(order); + // root^{2^i} != 1 for all 0 <= i < order + for (size_t i = 0; i < order; i++) { + EXPECT_NE(root, Field::one()); + root = root.sqr(); + } + // root^{2^order} = 1 + EXPECT_EQ(root, Field::one()); + } +} + +// Verify that coset_generator() is not a quadratic residue mod p, i.e. coset_gen^{(p-1)/2} != 1. +TYPED_TEST_P(FieldConstantsTest, CosetGenerator) +{ + using Params = typename TypeParam::Params; + using Field = typename TypeParam::Field; + uint256_t mod{ Params::modulus_0, Params::modulus_1, Params::modulus_2, Params::modulus_3 }; + Field coset_gen = Field::coset_generator(); + EXPECT_NE(coset_gen.pow((mod - 1) / 2), Field::one()); +} + +// ============================================================ +// WASM consistency tests (9 x 29-bit limb representation) +// ============================================================ + +// Verify that the 9 x 29-bit WASM limbs reconstruct to the same modulus as the 4 x 64-bit limbs, +// and that each limb fits in 29 bits. +TYPED_TEST_P(FieldConstantsTest, WasmModulusConsistency) +{ + using Params = typename TypeParam::Params; + uint256_t mod{ Params::modulus_0, Params::modulus_1, Params::modulus_2, Params::modulus_3 }; + constexpr std::array wasm_limbs = { Params::modulus_wasm_0, Params::modulus_wasm_1, + Params::modulus_wasm_2, Params::modulus_wasm_3, + Params::modulus_wasm_4, Params::modulus_wasm_5, + Params::modulus_wasm_6, Params::modulus_wasm_7, + Params::modulus_wasm_8 }; + uint512_t wasm_modulus = 0; + for (size_t i = 0; i < 9; i++) { + wasm_modulus += uint512_t(wasm_limbs[i]) << (29UL * i); + EXPECT_LT(wasm_limbs[i], uint64_t(1) << 29); + } + EXPECT_EQ(wasm_modulus.lo, mod); + EXPECT_EQ(wasm_modulus.hi, uint256_t(0)); +} + +// Verify R_wasm^2 mod p, where R_wasm = 2^261 = 2^(29*9). +TYPED_TEST_P(FieldConstantsTest, WasmRSquared) +{ + using Params = typename TypeParam::Params; + uint256_t mod{ Params::modulus_0, Params::modulus_1, Params::modulus_2, Params::modulus_3 }; + uint512_t R_wasm = uint512_t(1) << 261; + uint512_t R_wasm_mod = R_wasm % mod; + uint512_t expected = (R_wasm_mod * R_wasm_mod) % mod; + uint256_t actual{ + Params::r_squared_wasm_0, Params::r_squared_wasm_1, Params::r_squared_wasm_2, Params::r_squared_wasm_3 + }; + EXPECT_EQ(expected.lo, actual); + EXPECT_EQ(expected.hi, uint256_t(0)); +} + +// Verify r_inv_wasm_{0-8} = 2^{-29} mod p, stored as 9 x 29-bit limbs. +// Also verify each limb fits in 29 bits, and that it is smaller than the modulus +TYPED_TEST_P(FieldConstantsTest, WasmPowMinus29) +{ + using Params = typename TypeParam::Params; + uint256_t mod{ Params::modulus_0, Params::modulus_1, Params::modulus_2, Params::modulus_3 }; + constexpr std::array r_inv_wasm_limbs = { + Params::r_inv_wasm_0, Params::r_inv_wasm_1, Params::r_inv_wasm_2, Params::r_inv_wasm_3, Params::r_inv_wasm_4, + Params::r_inv_wasm_5, Params::r_inv_wasm_6, Params::r_inv_wasm_7, Params::r_inv_wasm_8 + }; + uint512_t r_inv_wasm = 0; + for (size_t i = 0; i < 9; i++) { + r_inv_wasm += uint512_t(r_inv_wasm_limbs[i]) << (29UL * i); + EXPECT_LT(r_inv_wasm_limbs[i], uint64_t(1) << 29); + } + uint512_t two_29 = uint512_t(1) << 29; + uint512_t expected = two_29.invmod(mod); + EXPECT_EQ(r_inv_wasm, expected); + EXPECT_LT(r_inv_wasm, uint512_t(mod)); +} + +// Verify cube_root_wasm is the same field element as cube_root_native but in WASM Montgomery form. +// Since R_wasm = 2^261 and R_native = 2^256, converting native -> WASM multiplies by 2^5 = 32. +TYPED_TEST_P(FieldConstantsTest, WasmCubeRootConsistency) +{ + if constexpr (!TypeParam::has_cube_root) { + GTEST_SKIP() << "Cube root is not used for this field"; + } else { + using Params = typename TypeParam::Params; + uint256_t mod{ Params::modulus_0, Params::modulus_1, Params::modulus_2, Params::modulus_3 }; + uint256_t cube_root_native{ + Params::cube_root_0, Params::cube_root_1, Params::cube_root_2, Params::cube_root_3 + }; + uint256_t cube_root_wasm{ + Params::cube_root_wasm_0, Params::cube_root_wasm_1, Params::cube_root_wasm_2, Params::cube_root_wasm_3 + }; + // R_wasm / R_native = 2^261 / 2^256 = 2^5 = 32 + uint512_t expected = (uint512_t(cube_root_native) * 32) % mod; + EXPECT_EQ(expected.lo, cube_root_wasm); + } +} + +// Verify primitive_root_wasm is the same field element as primitive_root_native in WASM Montgomery form. +TYPED_TEST_P(FieldConstantsTest, WasmPrimitiveRootConsistency) +{ + if constexpr (!TypeParam::has_primitive_root) { + GTEST_SKIP() << "Primitive root is not used for this field"; + } else { + using Params = typename TypeParam::Params; + uint256_t mod{ Params::modulus_0, Params::modulus_1, Params::modulus_2, Params::modulus_3 }; + uint256_t primitive_root_native{ + Params::primitive_root_0, Params::primitive_root_1, Params::primitive_root_2, Params::primitive_root_3 + }; + uint256_t primitive_root_wasm{ Params::primitive_root_wasm_0, + Params::primitive_root_wasm_1, + Params::primitive_root_wasm_2, + Params::primitive_root_wasm_3 }; + // R_wasm / R_native = 2^261 / 2^256 = 2^5 = 32 + uint512_t expected = (uint512_t(primitive_root_native) * 32) % mod; + EXPECT_EQ(expected.lo, primitive_root_wasm); + } +} + +// Verify coset_generator_wasm is the same field element as coset_generator_native in WASM Montgomery form. +TYPED_TEST_P(FieldConstantsTest, CosetGeneratorConsistency) +{ + using Params = typename TypeParam::Params; + uint256_t mod{ Params::modulus_0, Params::modulus_1, Params::modulus_2, Params::modulus_3 }; + uint256_t coset_generator_native{ + Params::coset_generator_0, Params::coset_generator_1, Params::coset_generator_2, Params::coset_generator_3 + }; + uint256_t coset_generator_wasm{ Params::coset_generator_wasm_0, + Params::coset_generator_wasm_1, + Params::coset_generator_wasm_2, + Params::coset_generator_wasm_3 }; + // R_wasm / R_native = 2^261 / 2^256 = 2^5 = 32 + uint512_t expected = (static_cast(coset_generator_native) * 32) % mod; + EXPECT_EQ(expected, static_cast(coset_generator_wasm)); +} + +REGISTER_TYPED_TEST_SUITE_P(FieldConstantsTest, + Modulus, + RSquared, + RInv, + PowMinus64, + CubeRootOfUnity, + PrimitiveRootOfUnity, + CosetGenerator, + WasmModulusConsistency, + WasmRSquared, + WasmPowMinus29, + WasmCubeRootConsistency, + WasmPrimitiveRootConsistency, + CosetGeneratorConsistency); + +using FieldTestTypes = ::testing::Types; + +INSTANTIATE_TYPED_TEST_SUITE_P(AllFields, FieldConstantsTest, FieldTestTypes); diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/grumpkin.hpp b/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/grumpkin.hpp index 27101586a9ac..3c38ecd19a5c 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/grumpkin.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/grumpkin.hpp @@ -1,5 +1,5 @@ // === AUDIT STATUS === -// internal: { status: Planned, auditors: [], commit: } +// internal: { status: Completed, auditors: [Federico], commit: } // external_1: { status: not started, auditors: [], commit: } // external_2: { status: not started, auditors: [], commit: } // ===================== @@ -12,7 +12,10 @@ namespace bb::grumpkin { +// Max num bits such that all numbers represented by that many bits are smaller than fr::modulus constexpr size_t MAX_NO_WRAP_INTEGER_BIT_LENGTH = 252; +static_assert((uint256_t(1) << (MAX_NO_WRAP_INTEGER_BIT_LENGTH + 1)) - 1 < fr::modulus, + "MAX_NO_WRAP_INTEGER_BIT_LENGTH is too large"); using fq = bb::fr; using fr = bb::fq; @@ -22,7 +25,6 @@ struct G1Params { static constexpr bool can_hash_to_curve = true; static constexpr bool small_elements = true; static constexpr bool has_a = false; -// have checked in grumpkin.test_b that b is Montgomery form of -17 #if defined(__SIZEOF_INT128__) && !defined(__wasm__) static constexpr bb::fr b{ 0xdd7056026000005a, 0x223fa97acb319311, 0xcc388229877910c0, 0x34394632b724eaa }; #else @@ -30,7 +32,7 @@ struct G1Params { #endif static constexpr bb::fr a{ 0UL, 0UL, 0UL, 0UL }; - // generator point = (x, y) = (1, sqrt(-16)), sqrt(-16) = 4i + // generator point = (x, y) = (1, sqrt(-16)) = (1, -4i) static constexpr bb::fr one_x = bb::fr::one(); #if defined(__SIZEOF_INT128__) && !defined(__wasm__) static constexpr bb::fr one_y{ @@ -63,9 +65,6 @@ class Grumpkin { using AffineElement = typename Group::affine_element; static constexpr const char* name = "Grumpkin"; - // TODO(#673): This flag is temporary. It is needed in the verifier classes (GeminiVerifier, etc.) while these - // classes are instantiated with "native" curve types. Eventually, the verifier classes will be instantiated only - // with stdlib types, and "native" verification will be acheived via a simulated builder. static constexpr bool is_stdlib_type = false; // Required by SmallSubgroupIPA argument. This constant needs to divide the size of the multiplicative subgroup of diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/grumpkin.test.cpp b/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/grumpkin.test.cpp index 9cb34c57740a..f2976d22423d 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/grumpkin.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/grumpkin.test.cpp @@ -23,6 +23,10 @@ typename Group::affine_element naive_scalar_mul(const typename Group::element& b } } // namespace +// ========================= +// Parameter-related tests +// ========================= + TEST(grumpkin, CheckB) { auto b = grumpkin::g1::curve_b; @@ -30,6 +34,29 @@ TEST(grumpkin, CheckB) EXPECT_EQ(seventeen, -b); } +TEST(grumpkin, SubgroupGenerator) +{ + bb::fq subgroup_generator = bb::curve::Grumpkin::subgroup_generator; + bb::fq subgroup_generator_inverse = bb::curve::Grumpkin::subgroup_generator_inverse; + + EXPECT_NE(subgroup_generator.pow(3), fq::one()); + EXPECT_NE(subgroup_generator.pow(29), fq::one()); + EXPECT_EQ(subgroup_generator.pow(87), fq::one()); + EXPECT_EQ(subgroup_generator * subgroup_generator_inverse, fq::one()); +} + +TEST(grumpkin, OneYIsCorrect) +{ + fr one_y = bb::grumpkin::G1Params::one_y; + auto [_, expected] = (bb::grumpkin::G1Params::b + fr::one()).sqrt(); + + EXPECT_EQ(one_y, -expected); +} + +// ========================= +// Group-related tests +// ========================= + TEST(grumpkin, RandomElement) { grumpkin::g1::element result = grumpkin::g1::element::random_element(); diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/secp256k1/secp256k1.hpp b/barretenberg/cpp/src/barretenberg/ecc/curves/secp256k1/secp256k1.hpp index 3e68f5e2bbec..ba2192672282 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/secp256k1/secp256k1.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/secp256k1/secp256k1.hpp @@ -1,5 +1,5 @@ // === AUDIT STATUS === -// internal: { status: Planned, auditors: [], commit: } +// internal: { status: Completed, auditors: [Federico], commit: } // external_1: { status: not started, auditors: [], commit: } // external_2: { status: not started, auditors: [], commit: } // ===================== @@ -13,6 +13,16 @@ // NOLINTBEGIN(cppcoreguidelines-avoid-c-arrays) namespace bb::secp256k1 { + +/** + * @brief Parameters defining the base field of the secp256k1 curve. + * + * @details When split into 4 64-bit words, the parameters are represented in little-endian, i.e. the least significant + * bit comes first. For example, to recover the modulus from the 64-bit words we concatenate its limbs to obtain: + * 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f + * + * @note These parameters can be extracted by running the script parameter_helper.py in ecc/fields + */ struct FqParams { // There is a helper script in ecc/fields/parameter_helper.py that can be used to extract these parameters from the // source code @@ -29,29 +39,15 @@ struct FqParams { static constexpr uint64_t r_squared_2 = 0; static constexpr uint64_t r_squared_3 = 0; - // Coset generators in Montgomery form for R=2^256 mod Modulus. Used in FFT-based proving systems - static constexpr uint64_t coset_generators_0[8]{ - 0x300000b73ULL, 0x400000f44ULL, 0x500001315ULL, 0x6000016e6ULL, - 0x700001ab7ULL, 0x800001e88ULL, 0x900002259ULL, 0xa0000262aULL, - }; - static constexpr uint64_t coset_generators_1[8]{ - 0, 0, 0, 0, 0, 0, 0, 0, - }; - static constexpr uint64_t coset_generators_2[8]{ - 0, 0, 0, 0, 0, 0, 0, 0, - }; - static constexpr uint64_t coset_generators_3[8]{ - 0, 0, 0, 0, 0, 0, 0, 0, - }; - // -(Modulus^-1) mod 2^64 - // This is used to compute k = r_inv * lower_limb(scalar), such that scalar + k*modulus in integers would have 0 in - // the lowest limb By performing this sequentially for 4 limbs, we get an 8-limb representation of the scalar, where - // the lowest 4 limbs are zeros. Then we can immediately divide by 2^256 by simply getting rid of the lowest 4 limbs + // This constant is used during multiplication: given an 8-limb representation of the multiplication of two field + // elements, for each of the lowest four limbs we compute: k_i = r_inv * limb_i and we add 2^{64 * i} * k_i * p to + // the result of the multiplication. In this way we zero out the lowest four limbs of the multiplication and we can + // divide by 2^256 by taking the highest four limbs. See field_docs.hpp for more details. static constexpr uint64_t r_inv = 15580212934572586289ULL; // 2^(-64) mod Modulus - // Used in the reduction mechanism from https://hackmd.io/@Ingonyama/Barret-Montgomery + // Used in the reduction mechanism, see field_docs.md // Instead of computing k, we multiply the lowest limb by this value and then add to the following 5 limbs. // This saves us from having to compute k static constexpr uint64_t r_inv_0 = 0xffffffff27c7f3a9UL; @@ -59,20 +55,6 @@ struct FqParams { static constexpr uint64_t r_inv_2 = 0xffffffffffffffffUL; static constexpr uint64_t r_inv_3 = 0xd838091dd2253530UL; - // 2^(-29) mod Modulus - // Used in the reduction mechanism from https://hackmd.io/@Ingonyama/Barret-Montgomery - // Instead of computing k, we multiply the lowest limb by this value and then add to the following 10 limbs. - // This saves us from having to compute k - static constexpr uint64_t r_inv_wasm_0 = 0xed6544e; - static constexpr uint64_t r_inv_wasm_1 = 0x1ffffffb; - static constexpr uint64_t r_inv_wasm_2 = 0x1fffffff; - static constexpr uint64_t r_inv_wasm_3 = 0x1fffffff; - static constexpr uint64_t r_inv_wasm_4 = 0x1fffffff; - static constexpr uint64_t r_inv_wasm_5 = 0x1fffffff; - static constexpr uint64_t r_inv_wasm_6 = 0x1fffffff; - static constexpr uint64_t r_inv_wasm_7 = 0x10ffffff; - static constexpr uint64_t r_inv_wasm_8 = 0x9129a9; - // A little-endian representation of the cubic root of 1 in Fq in Montgomery form split into 4 64-bit words static constexpr uint64_t cube_root_0 = 0x58a4361c8e81894eULL; static constexpr uint64_t cube_root_1 = 0x03fde1631c4b80afULL; @@ -85,6 +67,12 @@ struct FqParams { static constexpr uint64_t primitive_root_2 = 0UL; static constexpr uint64_t primitive_root_3 = 0UL; + // Coset generators in Montgomery form for R=2^256 mod Modulus. Used in FFT-based proving systems + static constexpr uint64_t coset_generator_0 = 0x300000b73ULL; + static constexpr uint64_t coset_generator_1 = 0; + static constexpr uint64_t coset_generator_2 = 0; + static constexpr uint64_t coset_generator_3 = 0; + // A little-endian representation of the modulus split into 9 29-bit limbs // This is used in wasm because we can only do multiplication with 64-bit result instead of 128-bit like in x86_64 static constexpr uint64_t modulus_wasm_0 = 0x1ffffc2f; @@ -104,6 +92,20 @@ struct FqParams { static constexpr uint64_t r_squared_wasm_2 = 0x0000000000000000UL; static constexpr uint64_t r_squared_wasm_3 = 0x0000000000000000UL; + // 2^(-29) mod Modulus + // Used in the reduction mechanism, see field_docs.md + // Instead of computing k, we multiply the lowest limb by this value and then add to the following 10 limbs. + // This saves us from having to compute k + static constexpr uint64_t r_inv_wasm_0 = 0xed6544e; + static constexpr uint64_t r_inv_wasm_1 = 0x1ffffffb; + static constexpr uint64_t r_inv_wasm_2 = 0x1fffffff; + static constexpr uint64_t r_inv_wasm_3 = 0x1fffffff; + static constexpr uint64_t r_inv_wasm_4 = 0x1fffffff; + static constexpr uint64_t r_inv_wasm_5 = 0x1fffffff; + static constexpr uint64_t r_inv_wasm_6 = 0x1fffffff; + static constexpr uint64_t r_inv_wasm_7 = 0x10ffffff; + static constexpr uint64_t r_inv_wasm_8 = 0x9129a9; + // A little-endian representation of the cube root of 1 in Fq in Montgomery form for wasm (R=2^261 mod modulus) // split into 4 64-bit words static constexpr uint64_t cube_root_wasm_0 = 0x1486c3a0d03162ffUL; @@ -119,22 +121,10 @@ struct FqParams { // Coset generators in Montgomery form for R=2^261 mod Modulus. Used in FFT-based proving systems, don't really need // them here - static constexpr uint64_t coset_generators_wasm_0[8] = { 0x0000006000016e60ULL, 0x000000800001e880ULL, - 0x000000a0000262a0ULL, 0x000000c00002dcc0ULL, - 0x000000e0000356e0ULL, 0x000001000003d100ULL, - 0x0000012000044b20ULL, 0x000001400004c540ULL }; - static constexpr uint64_t coset_generators_wasm_1[8] = { 0x0000000000000000ULL, 0x0000000000000000ULL, - 0x0000000000000000ULL, 0x0000000000000000ULL, - 0x0000000000000000ULL, 0x0000000000000000ULL, - 0x0000000000000000ULL, 0x0000000000000000ULL }; - static constexpr uint64_t coset_generators_wasm_2[8] = { 0x0000000000000000ULL, 0x0000000000000000ULL, - 0x0000000000000000ULL, 0x0000000000000000ULL, - 0x0000000000000000ULL, 0x0000000000000000ULL, - 0x0000000000000000ULL, 0x0000000000000000ULL }; - static constexpr uint64_t coset_generators_wasm_3[8] = { 0x0000000000000000ULL, 0x0000000000000000ULL, - 0x0000000000000000ULL, 0x0000000000000000ULL, - 0x0000000000000000ULL, 0x0000000000000000ULL, - 0x0000000000000000ULL, 0x0000000000000000ULL }; + static constexpr uint64_t coset_generator_wasm_0 = 0x0000006000016e60ULL; + static constexpr uint64_t coset_generator_wasm_1 = 0; + static constexpr uint64_t coset_generator_wasm_2 = 0; + static constexpr uint64_t coset_generator_wasm_3 = 0; // For consistency with bb::fq, if we ever represent an element of bb::secp256k1::fq in the public inputs, we do so // as a bigfield element, so with 4 public inputs @@ -144,6 +134,15 @@ struct FqParams { }; using fq = field; +/** + * @brief Parameters defining the scalar field of the secp256k1 curve. + * + * @details When split into 4 64-bit words, the parameters are represented in little-endian, i.e. the least significant + * bit comes first. For example, to recover the modulus from the 64-bit words we concatenate its limbs to obtain: + * 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 + * + * @note These parameters can be extracted by running the script parameter_helper.py in ecc/fields + */ struct FrParams { // A little-endian representation of the modulus split into 4 64-bit words @@ -165,7 +164,7 @@ struct FrParams { static constexpr uint64_t r_inv = 5408259542528602431ULL; // 2^(-64) mod Modulus - // Used in the reduction mechanism from https://hackmd.io/@Ingonyama/Barret-Montgomery + // Used in the reduction mechanism, see field_docs.md // Instead of computing k, we multiply the lowest limb by this value and then add to the following 5 limbs. // This saves us from having to compute k static constexpr uint64_t r_inv_0 = 0x9d4ad302583de6dcUL; @@ -173,66 +172,25 @@ struct FrParams { static constexpr uint64_t r_inv_2 = 0xffffffffffffffffUL; static constexpr uint64_t r_inv_3 = 0x4b0dff665588b13eUL; - // 2^(-29) mod Modulus - // Used in the reduction mechanism from https://hackmd.io/@Ingonyama/Barret-Montgomery - // Instead of computing k, we multiply the lowest limb by this value and then add to the following 10 limbs. - // This saves us from having to compute k - static constexpr uint64_t r_inv_wasm_0 = 0x3d864e; - static constexpr uint64_t r_inv_wasm_1 = 0x8b9f61c; - static constexpr uint64_t r_inv_wasm_2 = 0x3df60c0; - static constexpr uint64_t r_inv_wasm_3 = 0xa3c71eb; - static constexpr uint64_t r_inv_wasm_4 = 0x1ffff251; - static constexpr uint64_t r_inv_wasm_5 = 0x1fffffff; - static constexpr uint64_t r_inv_wasm_6 = 0x1fffffff; - static constexpr uint64_t r_inv_wasm_7 = 0x1effffff; - static constexpr uint64_t r_inv_wasm_8 = 0xac4589; - - // Coset generators in Montgomery form for R=2^261 mod Modulus. Used in FFT-based proving systems, don't really need - // them here - static constexpr uint64_t coset_generators_0[8]{ - 0x40e4273feef0b9bbULL, 0x8111c8b31eba787aULL, 0xc13f6a264e843739ULL, 0x16d0b997e4df5f8ULL, - 0x419aad0cae17b4b7ULL, 0x81c84e7fdde17376ULL, 0xc1f5eff30dab3235ULL, 0x22391663d74f0f4ULL, - }; - static constexpr uint64_t coset_generators_1[8]{ - 0x5a95af7e9394ded5ULL, 0x9fe6d297e44c3e99ULL, 0xe537f5b135039e5dULL, 0x2a8918ca85bafe22ULL, - 0x6fda3be3d6725de6ULL, 0xb52b5efd2729bdaaULL, 0xfa7c821677e11d6eULL, 0x3fcda52fc8987d33ULL, - }; - static constexpr uint64_t coset_generators_2[8]{ - 0x6ULL, 0x7ULL, 0x8ULL, 0xaULL, 0xbULL, 0xcULL, 0xdULL, 0xfULL, - }; - static constexpr uint64_t coset_generators_3[8]{ - 0, 0, 0, 0, 0, 0, 0, 0, - }; - // A little-endian representation of the cubic root of 1 in Fr in Montgomery form split into 4 64-bit words static constexpr uint64_t cube_root_0 = 0xf07deb3dc9926c9eULL; static constexpr uint64_t cube_root_1 = 0x2c93e7ad83c6944cULL; static constexpr uint64_t cube_root_2 = 0x73a9660652697d91ULL; static constexpr uint64_t cube_root_3 = 0x532840178558d639ULL; - // Not needed, since there is no endomorphism for secp256k1 - static constexpr uint64_t endo_minus_b1_lo = 0x6F547FA90ABFE4C3ULL; - static constexpr uint64_t endo_minus_b1_mid = 0xE4437ED6010E8828ULL; - - static constexpr uint64_t endo_b2_lo = 0xe86c90e49284eb15ULL; - static constexpr uint64_t endo_b2_mid = 0x3086d221a7d46bcdULL; - - static constexpr uint64_t endo_g1_lo = 0xE893209A45DBB031ULL; - static constexpr uint64_t endo_g1_mid = 0x3DAA8A1471E8CA7FULL; - static constexpr uint64_t endo_g1_hi = 0xE86C90E49284EB15ULL; - static constexpr uint64_t endo_g1_hihi = 0x3086D221A7D46BCDULL; - - static constexpr uint64_t endo_g2_lo = 0x1571B4AE8AC47F71ULL; - static constexpr uint64_t endo_g2_mid = 0x221208AC9DF506C6ULL; - static constexpr uint64_t endo_g2_hi = 0x6F547FA90ABFE4C4ULL; - static constexpr uint64_t endo_g2_hihi = 0xE4437ED6010E8828ULL; - // Not used in secp256k1 static constexpr uint64_t primitive_root_0 = 0UL; static constexpr uint64_t primitive_root_1 = 0UL; static constexpr uint64_t primitive_root_2 = 0UL; static constexpr uint64_t primitive_root_3 = 0UL; + // Coset generators in Montgomery form for R=2^261 mod Modulus. Used in FFT-based proving systems, don't really need + // them here + static constexpr uint64_t coset_generator_0 = 0x40e4273feef0b9bbULL; + static constexpr uint64_t coset_generator_1 = 0x5a95af7e9394ded5ULL; + static constexpr uint64_t coset_generator_2 = 0x6ULL; + static constexpr uint64_t coset_generator_3 = 0x0ULL; + // A little-endian representation of the modulus split into 9 29-bit limbs // This is used in wasm because we can only do multiplication with 64-bit result instead of 128-bit like in x86_64 static constexpr uint64_t modulus_wasm_0 = 0x10364141; @@ -252,6 +210,20 @@ struct FrParams { static constexpr uint64_t r_squared_wasm_2 = 0x5fd7916f341f1cefUL; static constexpr uint64_t r_squared_wasm_3 = 0x9c7356071a6f179aUL; + // 2^(-29) mod Modulus + // Used in the reduction mechanism, see field_docs.md + // Instead of computing k, we multiply the lowest limb by this value and then add to the following 10 limbs. + // This saves us from having to compute k + static constexpr uint64_t r_inv_wasm_0 = 0x3d864e; + static constexpr uint64_t r_inv_wasm_1 = 0x8b9f61c; + static constexpr uint64_t r_inv_wasm_2 = 0x3df60c0; + static constexpr uint64_t r_inv_wasm_3 = 0xa3c71eb; + static constexpr uint64_t r_inv_wasm_4 = 0x1ffff251; + static constexpr uint64_t r_inv_wasm_5 = 0x1fffffff; + static constexpr uint64_t r_inv_wasm_6 = 0x1fffffff; + static constexpr uint64_t r_inv_wasm_7 = 0x1effffff; + static constexpr uint64_t r_inv_wasm_8 = 0xac4589; + // A little-endian representation of the cube root of 1 in Fr in Montgomery form for wasm (R=2^261 mod modulus) // split into 4 64-bit words static constexpr uint64_t cube_root_wasm_0 = 0x9185b639102f0736UL; @@ -265,24 +237,28 @@ struct FrParams { static constexpr uint64_t primitive_root_wasm_2 = 0x0000000000000000UL; static constexpr uint64_t primitive_root_wasm_3 = 0x0000000000000000UL; + // Not needed, since there is no endomorphism for secp256k1 + static constexpr uint64_t endo_minus_b1_lo = 0x6F547FA90ABFE4C3ULL; + static constexpr uint64_t endo_minus_b1_mid = 0xE4437ED6010E8828ULL; + + static constexpr uint64_t endo_b2_lo = 0xe86c90e49284eb15ULL; + static constexpr uint64_t endo_b2_mid = 0x3086d221a7d46bcdULL; + + // 256-bit-shift constants: g1 = floor((-b1) * 2^256 / r), g2 = floor(b2 * 2^256 / r) + // See endomorphism_scalars.py compute_splitting_constants() for derivation. + static constexpr uint64_t endo_g1_lo = 0x6F547FA90ABFE4C4ULL; + static constexpr uint64_t endo_g1_mid = 0xE4437ED6010E8828ULL; + static constexpr uint64_t endo_g1_hi = 0x0ULL; + + static constexpr uint64_t endo_g2_lo = 0xE86C90E49284EB15ULL; + static constexpr uint64_t endo_g2_mid = 0x3086D221A7D46BCDULL; + // Coset generators in Montgomery form for R=2^261 mod Modulus. Used in FFT-based proving systems, don't really need // them here - static constexpr uint64_t coset_generators_wasm_0[8] = { 0x1c84e7fdde173760ULL, 0x22391663d74f0f40ULL, - 0x27ed44c9d086e720ULL, 0x2da1732fc9bebf00ULL, - 0x3355a195c2f696e0ULL, 0x3909cffbbc2e6ec0ULL, - 0x3ebdfe61b56646a0ULL, 0x44722cc7ae9e1e80ULL }; - static constexpr uint64_t coset_generators_wasm_1[8] = { 0x52b5efd2729bdaa8ULL, 0xfcda52fc8987d330ULL, - 0xa6feb626a073cbb8ULL, 0x51231950b75fc440ULL, - 0xfb477c7ace4bbcc8ULL, 0xa56bdfa4e537b550ULL, - 0x4f9042cefc23add8ULL, 0xf9b4a5f9130fa660ULL }; - static constexpr uint64_t coset_generators_wasm_2[8] = { 0x00000000000000cbULL, 0x00000000000000f3ULL, - 0x000000000000011cULL, 0x0000000000000145ULL, - 0x000000000000016dULL, 0x0000000000000196ULL, - 0x00000000000001bfULL, 0x00000000000001e7ULL }; - static constexpr uint64_t coset_generators_wasm_3[8] = { 0x0000000000000000ULL, 0x0000000000000000ULL, - 0x0000000000000000ULL, 0x0000000000000000ULL, - 0x0000000000000000ULL, 0x0000000000000000ULL, - 0x0000000000000000ULL, 0x0000000000000000ULL }; + static constexpr uint64_t coset_generator_wasm_0 = 0x1c84e7fdde173760ULL; + static constexpr uint64_t coset_generator_wasm_1 = 0x52b5efd2729bdaa8ULL; + static constexpr uint64_t coset_generator_wasm_2 = 0x00000000000000cbULL; + static constexpr uint64_t coset_generator_wasm_3 = 0x0000000000000000ULL; // For consistency with bb::fq, if we ever represent an element of bb::secp256k1::fr in the public inputs, we do so // as a bigfield element, so with 4 public inputs diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/secp256k1/secp256k1.test.cpp b/barretenberg/cpp/src/barretenberg/ecc/curves/secp256k1/secp256k1.test.cpp index 2af8a939dbc0..40bbc71bc852 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/secp256k1/secp256k1.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/secp256k1/secp256k1.test.cpp @@ -9,10 +9,23 @@ using namespace bb; // - barretenberg/ecc/fields/field.test.cpp (generic field tests) // - barretenberg/ecc/fields/prime_field.test.cpp (prime field specific tests) // The tests below are for the secp256k1 elliptic curve group operations. +TEST(secp256k1, CurveCoefficients) +{ + secp256k1::fq expected_a = secp256k1::fq(0); + secp256k1::fq expected_b = secp256k1::fq(7); + + EXPECT_EQ(secp256k1::G1Params::a, expected_a); + EXPECT_EQ(secp256k1::G1Params::b, expected_b); +} TEST(secp256k1, GeneratorOnCurve) { secp256k1::g1::element result = secp256k1::g1::one; + secp256k1::fq expected_x = secp256k1::fq("0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"); + secp256k1::fq expected_y = secp256k1::fq("0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8"); + + EXPECT_EQ(result.x, expected_x); + EXPECT_EQ(result.y, expected_y); EXPECT_EQ(result.on_curve(), true); } diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/secp256k1/secp256k1_endo_notes.hpp b/barretenberg/cpp/src/barretenberg/ecc/curves/secp256k1/secp256k1_endo_notes.hpp index 55de295e6cc0..2e42c954b2e9 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/secp256k1/secp256k1_endo_notes.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/secp256k1/secp256k1_endo_notes.hpp @@ -14,11 +14,9 @@ struct basis_vectors { uint64_t endo_g1_lo = 0; uint64_t endo_g1_mid = 0; uint64_t endo_g1_hi = 0; - uint64_t endo_g1_hihi = 0; uint64_t endo_g2_lo = 0; uint64_t endo_g2_mid = 0; uint64_t endo_g2_hi = 0; - uint64_t endo_g2_hihi = 0; uint64_t endo_minus_b1_lo = 0; uint64_t endo_minus_b1_mid = 0; uint64_t endo_b2_lo = 0; @@ -108,7 +106,7 @@ struct basis_vectors { } uint512_t minus_b1 = -b1; - uint512_t shift256 = uint512_t(1) << 384; + uint512_t shift256 = uint512_t(1) << 256; uint512_t g1 = (-b1 * shift256) / uint512_t(secp256k1::fr::modulus); uint512_t g2 = (b2 * shift256) / uint512_t(secp256k1::fr::modulus); @@ -116,11 +114,9 @@ struct basis_vectors { result.endo_g1_lo = g1.lo.data[0]; result.endo_g1_mid = g1.lo.data[1]; result.endo_g1_hi = g1.lo.data[2]; - result.endo_g1_hihi = g1.lo.data[3]; result.endo_g2_lo = g2.lo.data[0]; result.endo_g2_mid = g2.lo.data[1]; result.endo_g2_hi = g2.lo.data[2]; - result.endo_g2_hihi = g2.lo.data[3]; result.endo_minus_b1_lo = minus_b1.lo.data[0]; result.endo_minus_b1_mid = minus_b1.lo.data[1]; result.endo_b2_lo = b2.lo.data[0]; diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/secp256r1/secp256r1.hpp b/barretenberg/cpp/src/barretenberg/ecc/curves/secp256r1/secp256r1.hpp index f3507361eb61..de51a97b0eb8 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/secp256r1/secp256r1.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/secp256r1/secp256r1.hpp @@ -1,5 +1,5 @@ // === AUDIT STATUS === -// internal: { status: Planned, auditors: [], commit: } +// internal: { status: Completed, auditors: [Federico], commit: } // external_1: { status: not started, auditors: [], commit: } // external_2: { status: not started, auditors: [], commit: } // ===================== @@ -9,8 +9,18 @@ #include "../../fields/field.hpp" #include "../../groups/group.hpp" -namespace bb::secp256r1 { // NOLINTBEGIN(cppcoreguidelines-avoid-c-arrays) +namespace bb::secp256r1 { + +/** + * @brief Parameters defining the base field of the secp256r1 curve. + * + * @details When split into 4 64-bit words, the parameters are represented in little-endian, i.e. the least significant + * bit comes first. For example, to recover the modulus from the 64-bit words we concatenate its limbs to obtain: + * 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff + * + * @note These parameters can be extracted by running the script parameter_helper.py in ecc/fields + */ struct FqParams { static constexpr const char* schema_name = "secp256r1_fq"; @@ -27,13 +37,14 @@ struct FqParams { static constexpr uint64_t r_squared_3 = 21474836477ULL; // -(Modulus^-1) mod 2^64 - // This is used to compute k = r_inv * lower_limb(scalar), such that scalar + k*modulus in integers would have 0 in - // the lowest limb By performing this sequentially for 4 limbs, we get an 8-limb representation of the scalar, where - // the lowest 4 limbs are zeros. Then we can immediately divide by 2^256 by simply getting rid of the lowest 4 limbs + // This constant is used during multiplication: given an 8-limb representation of the multiplication of two field + // elements, for each of the lowest four limbs we compute: k_i = r_inv * limb_i and we add 2^{64 * i} * k_i * p to + // the result of the multiplication. In this way we zero out the lowest four limbs of the multiplication and we can + // divide by 2^256 by taking the highest four limbs. See field_docs.hpp for more details. static constexpr uint64_t r_inv = 1; // 2^(-64) mod Modulus - // Used in the reduction mechanism from https://hackmd.io/@Ingonyama/Barret-Montgomery + // Used in the reduction mechanism, see field_docs.md // Instead of computing k, we multiply the lowest limb by this value and then add to the following 5 limbs. // This saves us from having to compute k static constexpr uint64_t r_inv_0 = 0x100000000UL; @@ -41,38 +52,6 @@ struct FqParams { static constexpr uint64_t r_inv_2 = 0xffffffff00000001UL; static constexpr uint64_t r_inv_3 = 0x0UL; - // 2^(-29) mod Modulus - // Used in the reduction mechanism from https://hackmd.io/@Ingonyama/Barret-Montgomery - // Instead of computing k, we multiply the lowest limb by this value and then add to the following 10 limbs. - // This saves us from having to compute k - static constexpr uint64_t r_inv_wasm_0 = 0x0; - static constexpr uint64_t r_inv_wasm_1 = 0x0; - static constexpr uint64_t r_inv_wasm_2 = 0x200; - static constexpr uint64_t r_inv_wasm_3 = 0x0; - static constexpr uint64_t r_inv_wasm_4 = 0x0; - static constexpr uint64_t r_inv_wasm_5 = 0x40000; - static constexpr uint64_t r_inv_wasm_6 = 0x1fe00000; - static constexpr uint64_t r_inv_wasm_7 = 0xffffff; - static constexpr uint64_t r_inv_wasm_8 = 0x0; - - // Coset generators in Montgomery form for R=2^256 mod Modulus. Used in FFT-based proving systems, don't really need - // them here - static constexpr uint64_t coset_generators_0[8]{ - 0x3ULL, 0x4ULL, 0x5ULL, 0x6ULL, 0x7ULL, 0x8ULL, 0x9ULL, 0xaULL, - }; - static constexpr uint64_t coset_generators_1[8]{ - 0xfffffffd00000000ULL, 0xfffffffc00000000ULL, 0xfffffffb00000000ULL, 0xfffffffa00000000ULL, - 0xfffffff900000000ULL, 0xfffffff800000000ULL, 0xfffffff700000000ULL, 0xfffffff600000000ULL, - }; - static constexpr uint64_t coset_generators_2[8]{ - 0xffffffffffffffffULL, 0xffffffffffffffffULL, 0xffffffffffffffffULL, 0xffffffffffffffffULL, - 0xffffffffffffffffULL, 0xffffffffffffffffULL, 0xffffffffffffffffULL, 0xffffffffffffffffULL, - }; - static constexpr uint64_t coset_generators_3[8]{ - 0x2fffffffcULL, 0x3fffffffbULL, 0x4fffffffaULL, 0x5fffffff9ULL, - 0x6fffffff8ULL, 0x7fffffff7ULL, 0x8fffffff6ULL, 0x9fffffff5ULL, - }; - // Not used for secp256r1 static constexpr uint64_t cube_root_0 = 0UL; static constexpr uint64_t cube_root_1 = 0UL; @@ -85,6 +64,13 @@ struct FqParams { static constexpr uint64_t primitive_root_2 = 0UL; static constexpr uint64_t primitive_root_3 = 0UL; + // Coset generators in Montgomery form for R=2^256 mod Modulus. Used in FFT-based proving systems, don't really need + // them here + static constexpr uint64_t coset_generator_0 = 0x3ULL; + static constexpr uint64_t coset_generator_1 = 0xfffffffd00000000ULL; + static constexpr uint64_t coset_generator_2 = 0xffffffffffffffffULL; + static constexpr uint64_t coset_generator_3 = 0x2fffffffcULL; + // A little-endian representation of the modulus split into 9 29-bit limbs // This is used in wasm because we can only do multiplication with 64-bit result instead of 128-bit like in x86_64 static constexpr uint64_t modulus_wasm_0 = 0x1fffffff; @@ -104,6 +90,20 @@ struct FqParams { static constexpr uint64_t r_squared_wasm_2 = 0xfffffffffffffbffUL; static constexpr uint64_t r_squared_wasm_3 = 0x000013fffffff7ffUL; + // 2^(-29) mod Modulus + // Used in the reduction mechanism, see field_docs.md + // Instead of computing k, we multiply the lowest limb by this value and then add to the following 10 limbs. + // This saves us from having to compute k + static constexpr uint64_t r_inv_wasm_0 = 0x0; + static constexpr uint64_t r_inv_wasm_1 = 0x0; + static constexpr uint64_t r_inv_wasm_2 = 0x200; + static constexpr uint64_t r_inv_wasm_3 = 0x0; + static constexpr uint64_t r_inv_wasm_4 = 0x0; + static constexpr uint64_t r_inv_wasm_5 = 0x40000; + static constexpr uint64_t r_inv_wasm_6 = 0x1fe00000; + static constexpr uint64_t r_inv_wasm_7 = 0xffffff; + static constexpr uint64_t r_inv_wasm_8 = 0x0; + // Not used for secp256r1 static constexpr uint64_t cube_root_wasm_0 = 0x0000000000000000UL; static constexpr uint64_t cube_root_wasm_1 = 0x0000000000000000UL; @@ -118,22 +118,10 @@ struct FqParams { // Coset generators in Montgomery form for R=2^261 mod Modulus. Used in FFT-based proving systems, don't really need // them here - static constexpr uint64_t coset_generators_wasm_0[8] = { 0x0000000000000060ULL, 0x0000000000000080ULL, - 0x00000000000000a0ULL, 0x00000000000000c0ULL, - 0x00000000000000e0ULL, 0x0000000000000100ULL, - 0x0000000000000120ULL, 0x0000000000000140ULL }; - static constexpr uint64_t coset_generators_wasm_1[8] = { 0xffffffa000000000ULL, 0xffffff8000000000ULL, - 0xffffff6000000000ULL, 0xffffff4000000000ULL, - 0xffffff2000000000ULL, 0xffffff0000000000ULL, - 0xfffffee000000000ULL, 0xfffffec000000000ULL }; - static constexpr uint64_t coset_generators_wasm_2[8] = { 0xffffffffffffffffULL, 0xffffffffffffffffULL, - 0xffffffffffffffffULL, 0xffffffffffffffffULL, - 0xffffffffffffffffULL, 0xffffffffffffffffULL, - 0xffffffffffffffffULL, 0xffffffffffffffffULL }; - static constexpr uint64_t coset_generators_wasm_3[8] = { 0x0000005fffffff9fULL, 0x0000007fffffff7fULL, - 0x0000009fffffff5fULL, 0x000000bfffffff3fULL, - 0x000000dfffffff1fULL, 0x000000fffffffeffULL, - 0x0000011ffffffedfULL, 0x0000013ffffffebfULL }; + static constexpr uint64_t coset_generator_wasm_0 = 0x0000000000000060ULL; + static constexpr uint64_t coset_generator_wasm_1 = 0xffffffa000000000ULL; + static constexpr uint64_t coset_generator_wasm_2 = 0xffffffffffffffffULL; + static constexpr uint64_t coset_generator_wasm_3 = 0x0000005fffffff9fULL; // For consistency with bb::fq, if we ever represent an element of bb::secp256r1::fq in the public inputs, we do so // as a bigfield element, so with 4 public inputs @@ -141,6 +129,15 @@ struct FqParams { }; using fq = field; +/** + * @brief Parameters defining the scalar field of the secp256r1 curve. + * + * @details When split into 4 64-bit words, the parameters are represented in little-endian, i.e. the least significant + * bit comes first. For example, to recover the modulus from the 64-bit words we concatenate its limbs to obtain: + * 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551 + * + * @note These parameters can be extracted by running the script parameter_helper.py in ecc/fields + */ struct FrParams { static constexpr const char* schema_name = "secp256r1_fr"; @@ -163,7 +160,7 @@ struct FrParams { static constexpr uint64_t r_inv = 14758798090332847183ULL; // 2^(-64) mod Modulus - // Used in the reduction mechanism from https://hackmd.io/@Ingonyama/Barret-Montgomery + // Used in the reduction mechanism, see field_docs.md // Instead of computing k, we multiply the lowest limb by this value and then add to the following 5 limbs. // This saves us from having to compute k static constexpr uint64_t r_inv_0 = 0x230102a06d6251dcUL; @@ -171,38 +168,6 @@ struct FrParams { static constexpr uint64_t r_inv_2 = 0xded10c5bee00bc4eUL; static constexpr uint64_t r_inv_3 = 0xccd1c8aa212ef3a4UL; - // 2^(-29) mod Modulus - // Used in the reduction mechanism from https://hackmd.io/@Ingonyama/Barret-Montgomery - // Instead of computing k, we multiply the lowest limb by this value and then add to the following 5 limbs. - // This saves us from having to compute k - static constexpr uint64_t r_inv_wasm_0 = 0x8517c79; - static constexpr uint64_t r_inv_wasm_1 = 0x1edc694; - static constexpr uint64_t r_inv_wasm_2 = 0x459ee5c; - static constexpr uint64_t r_inv_wasm_3 = 0x705a6a8; - static constexpr uint64_t r_inv_wasm_4 = 0x1ffffe2a; - static constexpr uint64_t r_inv_wasm_5 = 0x113bffff; - static constexpr uint64_t r_inv_wasm_6 = 0x1621c017; - static constexpr uint64_t r_inv_wasm_7 = 0xef1ff43; - static constexpr uint64_t r_inv_wasm_8 = 0x7005e2; - - // Coset generators in Montgomery form for R=2^256 mod Modulus. Used in FFT-based proving systems, don't really need - // them here - static constexpr uint64_t coset_generators_0[8]{ - 0x55eb74ab1949fac9ULL, 0x6231a9e81ce6d578ULL, 0x6e77df252083b027ULL, 0x7abe146224208ad6ULL, - 0x8704499f27bd6585ULL, 0x934a7edc2b5a4034ULL, 0x9f90b4192ef71ae3ULL, 0xabd6e9563293f592ULL, - }; - static constexpr uint64_t coset_generators_1[8]{ - 0xd5af25406e5aaa5dULL, 0x18c82a92c7430bd8ULL, 0x5be12fe5202b6d53ULL, 0x9efa35377913ceceULL, - 0xe2133a89d1fc3049ULL, 0x252c3fdc2ae491c4ULL, 0x6845452e83ccf33fULL, 0xab5e4a80dcb554baULL, - }; - static constexpr uint64_t coset_generators_2[8]{ - 0x1ULL, 0x2ULL, 0x2ULL, 0x2ULL, 0x2ULL, 0x3ULL, 0x3ULL, 0x3ULL, - }; - static constexpr uint64_t coset_generators_3[8]{ - 0x6fffffff9ULL, 0x7fffffff8ULL, 0x8fffffff7ULL, 0x9fffffff6ULL, - 0xafffffff5ULL, 0xbfffffff4ULL, 0xcfffffff3ULL, 0xdfffffff2ULL, - }; - // Not used for secp256r1 static constexpr uint64_t cube_root_0 = 0UL; static constexpr uint64_t cube_root_1 = 0UL; @@ -215,6 +180,13 @@ struct FrParams { static constexpr uint64_t primitive_root_2 = 0UL; static constexpr uint64_t primitive_root_3 = 0UL; + // Coset generator in Montgomery form for R=2^256 mod Modulus. Used in FFT-based proving systems, don't really need + // them here + static constexpr uint64_t coset_generator_0 = 0x55eb74ab1949fac9ULL; + static constexpr uint64_t coset_generator_1 = 0xd5af25406e5aaa5dULL; + static constexpr uint64_t coset_generator_2 = 0x1ULL; + static constexpr uint64_t coset_generator_3 = 0x6fffffff9ULL; + // A little-endian representation of the modulus split into 9 29-bit limbs // This is used in wasm because we can only do multiplication with 64-bit result instead of 128-bit like in x86_64 static constexpr uint64_t modulus_wasm_0 = 0x1c632551; @@ -234,6 +206,20 @@ struct FrParams { static constexpr uint64_t r_squared_wasm_2 = 0x16c8e4adafb16586UL; static constexpr uint64_t r_squared_wasm_3 = 0x84b6556a65587f06UL; + // 2^(-29) mod Modulus + // Used in the reduction mechanism, see field_docs.md + // Instead of computing k, we multiply the lowest limb by this value and then add to the following 5 limbs. + // This saves us from having to compute k + static constexpr uint64_t r_inv_wasm_0 = 0x8517c79; + static constexpr uint64_t r_inv_wasm_1 = 0x1edc694; + static constexpr uint64_t r_inv_wasm_2 = 0x459ee5c; + static constexpr uint64_t r_inv_wasm_3 = 0x705a6a8; + static constexpr uint64_t r_inv_wasm_4 = 0x1ffffe2a; + static constexpr uint64_t r_inv_wasm_5 = 0x113bffff; + static constexpr uint64_t r_inv_wasm_6 = 0x1621c017; + static constexpr uint64_t r_inv_wasm_7 = 0xef1ff43; + static constexpr uint64_t r_inv_wasm_8 = 0x7005e2; + // Not used for secp256r1 static constexpr uint64_t cube_root_wasm_0 = 0x0000000000000000UL; static constexpr uint64_t cube_root_wasm_1 = 0x0000000000000000UL; @@ -248,22 +234,10 @@ struct FrParams { // Coset generators in Montgomery form for R=2^261 mod Modulus. Used in FFT-based proving systems, don't really need // them here - static constexpr uint64_t coset_generators_wasm_0[8] = { 0xbd6e9563293f5920ULL, 0x46353d039cdaaf00ULL, - 0xcefbe4a4107604e0ULL, 0x57c28c4484115ac0ULL, - 0xe08933e4f7acb0a0ULL, 0x694fdb856b480680ULL, - 0xf2168325dee35c60ULL, 0x7add2ac6527eb240ULL }; - static constexpr uint64_t coset_generators_wasm_1[8] = { 0xb5e4a80dcb554baaULL, 0x19055258e8617b0cULL, - 0x7c25fca4056daa6dULL, 0xdf46a6ef2279d9cfULL, - 0x4267513a3f860930ULL, 0xa587fb855c923892ULL, - 0x08a8a5d0799e67f3ULL, 0x6bc9501b96aa9755ULL }; - static constexpr uint64_t coset_generators_wasm_2[8] = { 0x000000000000003aULL, 0x0000000000000043ULL, - 0x000000000000004bULL, 0x0000000000000053ULL, - 0x000000000000005cULL, 0x0000000000000064ULL, - 0x000000000000006dULL, 0x0000000000000075ULL }; - static constexpr uint64_t coset_generators_wasm_3[8] = { 0x000000dfffffff20ULL, 0x000000ffffffff00ULL, - 0x0000011ffffffee0ULL, 0x0000013ffffffec0ULL, - 0x0000015ffffffea0ULL, 0x0000017ffffffe80ULL, - 0x0000019ffffffe60ULL, 0x000001bffffffe40ULL }; + static constexpr uint64_t coset_generator_wasm_0 = 0xbd6e9563293f5920ULL; + static constexpr uint64_t coset_generator_wasm_1 = 0xb5e4a80dcb554baaULL; + static constexpr uint64_t coset_generator_wasm_2 = 0x000000000000003aULL; + static constexpr uint64_t coset_generator_wasm_3 = 0x000000dfffffff20ULL; // For consistency with bb::fq, if we ever represent an element of bb::secp256r1::fq in the public inputs, we do so // as a bigfield element, so with 4 public inputs diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/secp256r1/secp256r1.test.cpp b/barretenberg/cpp/src/barretenberg/ecc/curves/secp256r1/secp256r1.test.cpp index 95c157a28b73..cb63ad7b0c66 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/secp256r1/secp256r1.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/secp256r1/secp256r1.test.cpp @@ -10,9 +10,23 @@ using namespace bb; // - barretenberg/ecc/fields/prime_field.test.cpp (prime field specific tests) // The tests below are for the secp256r1 elliptic curve group operations. +TEST(secp256r1, CurveCoefficients) +{ + secp256r1::fq expected_a = secp256r1::fq("0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc"); + secp256r1::fq expected_b = secp256r1::fq("0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b"); + + EXPECT_EQ(secp256r1::G1Params::a, expected_a); + EXPECT_EQ(secp256r1::G1Params::b, expected_b); +} + TEST(secp256r1, GeneratorOnCurve) { secp256r1::g1::element result = secp256r1::g1::one; + secp256r1::fq expected_x = secp256r1::fq("0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296"); + secp256r1::fq expected_y = secp256r1::fq("0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5"); + + EXPECT_EQ(result.x, expected_x); + EXPECT_EQ(result.y, expected_y); EXPECT_EQ(result.on_curve(), true); } diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/types.hpp b/barretenberg/cpp/src/barretenberg/ecc/curves/types.hpp index ce590f41cefa..e2966d3f5e3c 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/types.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/types.hpp @@ -1,5 +1,5 @@ // === AUDIT STATUS === -// internal: { status: Planned, auditors: [], commit: } +// internal: { status: Completed, auditors: [Federico], commit: } // external_1: { status: not started, auditors: [], commit: } // external_2: { status: not started, auditors: [], commit: } // ===================== diff --git a/barretenberg/cpp/src/barretenberg/ecc/fields/endomorphism_scalars.py b/barretenberg/cpp/src/barretenberg/ecc/fields/endomorphism_scalars.py index 5f0152d2f5e3..69961ad7f210 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/fields/endomorphism_scalars.py +++ b/barretenberg/cpp/src/barretenberg/ecc/fields/endomorphism_scalars.py @@ -5,10 +5,12 @@ This document explains the "splitting scalars" algorithm in Barretenberg for all curves that admit an efficient endomorphism. We cover: - Part 0 (§0): Preliminaries — the GLV lattice and how to find a short basis - Part I (§1–§5): BN254 Fr — the scalar field of BN254 (254-bit, uses 2^256 shift) - Part II (§6–§9): BN254 Fq — the base field of BN254 (254-bit, uses 2^256 shift) - Part III (§10–§14): secp256k1 Fr — the scalar field of secp256k1 (256-bit, uses 2^384 shift) + Part 0 (§0): Preliminaries — the GLV lattice and how to find a short basis + Part I (§1–§5): BN254 Fr — the scalar field of BN254 (254-bit, uses 2^256 shift) + Part II (§6–§9): BN254 Fq — the base field of BN254 (254-bit, uses 2^256 shift) + Part III (§10–§14): secp256k1 Fr — the scalar field of secp256k1 (256-bit, now uses 2^256 shift) + Appendix (§16): 129-bit scalars for secp256k1 + Appendix (§17): Why the BN254 Fr and Fq lattice bases are nearly identical Reference: Gallant, Lambert, Vanstone, "Faster Point Multiplication on Elliptic Curves" (2001) @@ -55,14 +57,20 @@ # down through √p and below. We stop at the first remainder r_j < √p and read off # two short lattice vectors from the Bézout coefficients at steps j−1 and j. # -# The resulting vector sizes depend on the specific λ and p: +# The resulting vector sizes are generically ~√p, but the exact sizes determine +# whether the split scalars k1, k2 fit in 128 bits: # -# • BN254 (Fr and Fq): the curve is constructed from a 63-bit parameter x, and the -# lattice vectors are a1 = b2 = 2x+1 (64 bits), |b1| = 6x²+2x (127 bits). -# This asymmetric 64/127-bit pattern is a consequence of the BN parametrisation. +# • BN254 (Fr and Fq): p is 254 bits, so √p ~ 127 bits. The lattice vectors +# have |b1| ≤ 127 bits, |b2| ≤ 64 bits (the asymmetry comes from the explicit BN). +# Since k2 = f1·|b1| - f2·b2 with f1,f2 ∈ [0,1), we get |k2| < 2^127, +# which fits comfortably in 128 bits with 1 bit of headroom. Similarly, +# |a1| = 64 bits and |a2| = 127 bits, so similar logic applies for k1. # -# • secp256k1 Fr: no small generating parameter; the lattice vectors are all in the -# generic ~126–129-bit range (roughly √p for a 256-bit prime). +# • secp256k1 Fr: p is 256 bits, so √p ~ 128 bits. The lattice basis has +# |b1| = 128 bits and |b2| = 126 bits. However, |a1| = 126 bits and |a2| +# = 129 bits. This implies that the naive bound only gives that |k1| +# ≤ 129 bits. Indeed, k1 is 129 bits ~25% of the time. It turns out that |k2| +# is 129 bits roughly 0.3% of the time. # from math import isqrt @@ -92,24 +100,24 @@ def find_short_lattice_basis(lambda_val, modulus): # prev_coeff - quot·coeff. It follows that (-remainder, coeff) is always # a lattice vector: -remainder + λ·coeff ≡ -coeff·λ + coeff·λ ≡ 0. remainder, prev_remainder = lambda_val, modulus - coeff, prev_coeff = 1, 0 + coeff, prev_coeff = 1, 0 # Run until the remainder first drops below √p. while remainder >= approx_sqrt: - quot = prev_remainder // remainder + quot = prev_remainder // remainder prev_remainder, remainder = remainder, prev_remainder - quot * remainder - prev_coeff, coeff = coeff, prev_coeff - quot * coeff + prev_coeff, coeff = coeff, prev_coeff - quot * coeff # At this point: - # vec_before = (-prev_remainder, prev_coeff) — last step above √p - # vec_cross = (-remainder, coeff) — first step below √p + # vec_before = (-prev_remainder, prev_coeff) — last step above √p + # vec_cross = (-remainder, coeff) — first step below √p vec_before = (-prev_remainder, prev_coeff) - vec_cross = (-remainder, coeff) + vec_cross = (-remainder, coeff) # One more EEA step gives an independent candidate vector. - quot = prev_remainder // remainder - r_after = prev_remainder - quot * remainder - s_after = prev_coeff - quot * coeff + quot = prev_remainder // remainder + r_after = prev_remainder - quot * remainder + s_after = prev_coeff - quot * coeff vec_after = (-r_after, s_after) # First basis vector: vec_cross (shortest, by construction). @@ -126,16 +134,61 @@ def find_short_lattice_basis(lambda_val, modulus): return a1, b1, a2, b2 +# ==================================================================================== +# § 0a. THE 256-BIT-SHIFT APPROXIMATION — ERROR BOUND PROOF +# ==================================================================================== +# +# CLAIM: For any prime r < 2^256 and any b with |b| < r, define g = floor(b * 2^256 / r). +# Then for every k in [0, r): +# +# floor(g * k / 2^256) ∈ { floor(b * k / r), floor(b * k / r) - 1 } +# +# i.e., the approximation error is in {0, -1}. This holds for ALL curves with +# r < 2^256 — BN254, secp256k1, and any other. +# (Note that we used to use a 384 bit shift for secp256k1.) +# +# PROOF: +# +# Write the Euclidean division of b * 2^256 by r: +# +# b * 2^256 = g * r + ε where 0 ≤ ε < r ...(1) +# +# Rearranging: g = (b * 2^256 - ε) / r. Multiply both sides by k: +# +# g * k = b * k * 2^256 / r - ε * k / r +# +# Dividing by 2^256: +# +# g * k / 2^256 = b * k / r - ε * k / (r * 2^256) ...(2) +# +# The correction term δ := ε * k / (r * 2^256) satisfies: +# +# 0 ≤ δ = ε * k / (r * 2^256) < r * r / (r * 2^256) +# = r / 2^256 < 1 ...(3) +# +# (using ε < r, k < r, and r < 2^256). +# +# From (2) and (3): +# +# b*k/r - 1 < g*k/2^256 ≤ b*k/r ...(4) +# +# Taking floors of (4): if b*k/r = q + f where q = floor(b*k/r) and 0 ≤ f < 1, +# then g*k/2^256 ∈ (q + f - 1, q + f], so floor(g*k/2^256) ∈ {q-1, q}. ∎ + # ╔══════════════════════════════════════════════════════════════════════════════╗ -# ║ PART I: BN254 Fr (Scalar Field) ║ +# ║ PART I: BN254 Fr (Scalar Field) ║ # ╚══════════════════════════════════════════════════════════════════════════════╝ # ==================================================================================== # § 1. BN254 Fr — FIELD PARAMETERS # ==================================================================================== +# The BN parameter x (see §17 for why the Fr and Fq lattice bases are nearly identical) +x_bn = 4965661367192848881 # 0x44e992b44a6909f1, 63 bits + # The scalar field modulus of BN254 (from bn254/fr.hpp) r = 0x30644E72E131A029B85045B68181585D2833E84879B9709143E1F593F0000001 +assert r == 36*x_bn**4 + 36*x_bn**3 + 18*x_bn**2 + 6*x_bn + 1, "r = 36x⁴ + 36x³ + 18x² + 6x + 1" # Montgomery parameter: R = 2^256 mod r # This is needed because fr.hpp stores values in Montgomery form @@ -170,7 +223,13 @@ def find_short_lattice_basis(lambda_val, modulus): a2 = 0x6f4d8248eeb859fd0be4e1541221250b # 127 bits b2 = 0x89d3256894d213e3 # 64 bits -# NOTE: a remarkable feature of this short basis is that a1 == b2, and indeed -b1 is rather close to a2. +# NOTE: a1 == b2 (= 2x+1) and a2 ≈ |b1| (= 6x²+4x+1 vs 6x²+2x). +# This is a structural consequence of the BN parameterization; see §17 for the full explanation. +# In particular, we can verify these polynomial identities: +assert a1 == 2 * x_bn + 1, "a1 = 2x + 1" +assert b2 == 2 * x_bn + 1, "b2 = 2x + 1" +assert a2 == 6 * x_bn**2 + 4 * x_bn + 1, "a2 = 6x^2 + 4x + 1" +assert -b1 == 6 * x_bn**2 + 2 * x_bn, "|b1| = 6x^2 + 2x" # Verify that the vectors are in the lattice: ai + λ·bi ≡ 0 (mod r) assert (a1 + lambda_val * b1) % r == 0, "Lattice vector 1 must satisfy a1 + λ·b1 ≡ 0" @@ -228,14 +287,14 @@ def compute_splitting_constants(modulus, b1, b2): # ==================================================================================== # # Computes (k1, k2) with k ≡ k1 - λ·k2 (mod r) and |k1|, |k2| < 2^128. -# See §0 for the derivation (Babai's nearest plane). # # SUBTLETY — k2 CAN BE NEGATIVE: # # k2 = -δ1·|b1| + δ2·b2 where δ1, δ2 ∈ [0,1) are rounding errors. This is # negative when δ1·|b1| > δ2·b2. Since |b1|/b2 ≈ 2^63 for BN254, even tiny # δ1 can cause this. It happens at k ≈ ⌈m·2^256/endo_g2⌉ where c1 ticks up -# to m. Frequency: ~2^{-64} of all inputs. +# to m. Note that the ≈ means that it can happen for _many_ k around/slightly greater than that number. +# Frequency: ~2^{-64} of all inputs. # # FIX: When t1 > 128 bits (i.e. k2 < 0 wrapped mod r), add |b1|. This shifts # along the lattice vector (a1, b1), making k2 positive: @@ -333,7 +392,7 @@ def verify_split(k, k1, k2, t1, t2, lambda_val, modulus): # ╔══════════════════════════════════════════════════════════════════════════════╗ -# ║ PART II: BN254 Fq (Base Field) ║ +# ║ PART II: BN254 Fq (Base Field) ║ # ╚══════════════════════════════════════════════════════════════════════════════╝ # ==================================================================================== @@ -345,6 +404,8 @@ def verify_split(k, k1, k2, t1, t2, lambda_val, modulus): # The base field modulus of BN254 (from bn254/fq.hpp) fq_modulus = 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD47 +assert fq_modulus == 36*x_bn**4 + 36*x_bn**3 + 24*x_bn**2 + 6*x_bn + 1, "q = 36x⁴ + 36x³ + 24x² + 6x + 1" +assert fq_modulus - r == 6 * x_bn**2, "q − r = 6x²" # Montgomery parameter for Fq: R = 2^256 mod q fq_R = pow(2, 256, fq_modulus) @@ -382,6 +443,18 @@ def verify_split(k, k1, k2, t1, t2, lambda_val, modulus): fq_det = fq_a1 * fq_b2 - fq_a2 * fq_b1 assert abs(fq_det) == fq_modulus, f"Fq lattice determinant must be ±q, got {fq_det}" +# Verify polynomial structure and near-identity with Fr basis (see §17 for explanation): +assert fq_a1 == 2 * x_bn, "Fq: a1 = 2x" +assert fq_b2 == 2 * x_bn, "Fq: b2 = 2x" +assert fq_a2 == 6 * x_bn**2 + 4 * x_bn + 1, "Fq: a2 = 6x² + 4x + 1 (same as Fr!)" +assert -fq_b1 == 6 * x_bn**2 + 2 * x_bn + 1, "Fq: |b1| = 6x² + 2x + 1" + +# The Fr and Fq bases differ by at most 1 in each component: +assert a1 - fq_a1 == 1, "a1: Fr has 2x+1, Fq has 2x" +assert b2 - fq_b2 == 1, "b2: Fr has 2x+1, Fq has 2x" +assert fq_a2 == a2, "a2: identical for both fields" +assert (-fq_b1) - (-b1) == 1, "|b1|: Fq has 6x²+2x+1, Fr has 6x²+2x" + # ==================================================================================== # § 8. BN254 Fq — PRECOMPUTED CONSTANTS AND VERIFICATION @@ -432,7 +505,7 @@ def verify_split(k, k1, k2, t1, t2, lambda_val, modulus): # ╔══════════════════════════════════════════════════════════════════════════════╗ -# ║ PART III: secp256k1 Fr (Scalar Field) ║ +# ║ PART III: secp256k1 Fr (Scalar Field) ║ # ╚══════════════════════════════════════════════════════════════════════════════╝ # ==================================================================================== @@ -440,11 +513,16 @@ def verify_split(k, k1, k2, t1, t2, lambda_val, modulus): # ==================================================================================== # # secp256k1's scalar field modulus is a full 256 bits (top limb = 0xFFFF...), -# exceeding MODULUS_TOP_LIMB_LARGE_THRESHOLD (2^62). AUDITTODO: explain *exactly* the rounding issues that force this. -# This requires: -# - 2^384 shift instead of 2^256 (a >>256 shift loses precision for 256-bit moduli) -# - 4-limb endo_g constants (lo/mid/hi/hihi) -# - Montgomery field multiplication in split_into_endomorphism_scalars_384 +# exceeding MODULUS_TOP_LIMB_LARGE_THRESHOLD (2^62). As proved in §0a, the +# 256-bit shift approximation is sufficient for ANY prime r < 2^256 — the +# error is bounded to {0, -1}. The old 384-bit path was only needed because +# the 256-bit C++ code truncated outputs to 128 bits, clipping the 129th bit +# that appears for ~25% of inputs (k1) and ~0.3% of inputs (k2). With +# full-width output, 256-bit shift works perfectly. +# +# For secp256k1, the large-modulus branch of split_into_endomorphism_scalars +# returns full field elements (not truncated to 128 bits). The caller +# (biggroup_nafs.hpp) handles signs by inspecting the MSB of k2. # The scalar field modulus of secp256k1 (from secp256k1.hpp, FrParams) secp_r = ( @@ -477,7 +555,8 @@ def verify_split(k, k1, k2, t1, t2, lambda_val, modulus): # § 11. secp256k1 Fr — LATTICE BASIS # ==================================================================================== # -# See §0 for why these vectors are ~126–129 bits (unlike BN254's 64/127 pattern). +# See §0 for the component sizes: |a1| = 126, |b1| = 128, |a2| = 129, |b2| = 126 bits +# (unlike BN254's asymmetric 64/127 pattern). secp_a1, secp_b1, secp_a2, secp_b2 = find_short_lattice_basis(secp_lambda, secp_r) @@ -491,55 +570,26 @@ def verify_split(k, k1, k2, t1, t2, lambda_val, modulus): # ==================================================================================== -# § 12. secp256k1 Fr — PRECOMPUTED CONSTANTS (384-bit shift) +# § 12. secp256k1 Fr — PRECOMPUTED CONSTANTS (256-bit shift) # ==================================================================================== # -# In the 384-bit code, the naming is "cross-paired" — g1 is paired with minus_b1, -# and g2 is paired with b2 (the opposite of what you might expect): +# secp256k1 now uses the SAME 256-bit shift as BN254 (see §0a for the proof that +# this is sufficient for any r < 2^256). The naming convention matches BN254: # -# endo_g1 = ⌈b2 · 2^384 / r⌉ -# endo_g2 = ⌊(-b1) · 2^384 / r⌋ +# endo_g1 = ⌊(-b1) · 2^256 / r⌋ +# endo_g2 = ⌊b2 · 2^256 / r⌋ # -# Note: secp256k1_endo_notes.hpp uses the opposite naming convention for g1/g2, -# but the STORED values in FrParams follow this cross-paired convention. - -def compute_splitting_constants_384(modulus, b1, b2): - """ - Compute the precomputed constants for the 384-bit shift variant. +# We reuse compute_splitting_constants() from §3 — same function, same shift. - Returns (endo_g1, endo_g2, endo_minus_b1, endo_b2) matching the hpp file. - - Convention: endo_g1 is the b2-based approximation (cross-paired with minus_b1), - endo_g2 is the (-b1)-based approximation (cross-paired with b2). - """ - shift = 1 << 384 - # endo_g1 = ceil(b2 * 2^384 / r) — cross-paired with minus_b1 in the algorithm - endo_g1 = -((-b2 * shift) // modulus) - # endo_g2 = floor((-b1) * 2^384 / r) — cross-paired with b2 in the algorithm - endo_g2 = ((-b1) * shift) // modulus - endo_minus_b1 = (-b1) % modulus - endo_b2 = b2 % modulus - return endo_g1, endo_g2, endo_minus_b1, endo_b2 - - -secp_endo_g1, secp_endo_g2, secp_endo_minus_b1, secp_endo_b2 = compute_splitting_constants_384( +secp_endo_g1, secp_endo_g2, secp_endo_minus_b1, secp_endo_b2 = compute_splitting_constants( secp_r, secp_b1, secp_b2 ) # Verify these match the values in secp256k1.hpp (FrParams) -# endo_g1 is stored as (lo, mid, hi, hihi) — 4 × 64-bit limbs -secp_expected_endo_g1 = ( - 0xE893209A45DBB031 | - (0x3DAA8A1471E8CA7F << 64) | - (0xE86C90E49284EB15 << 128) | - (0x3086D221A7D46BCD << 192) -) -secp_expected_endo_g2 = ( - 0x1571B4AE8AC47F71 | - (0x221208AC9DF506C6 << 64) | - (0x6F547FA90ABFE4C4 << 128) | - (0xE4437ED6010E8828 << 192) -) +# endo_g1 is stored as (lo, mid, hi) — only 2 non-zero limbs for secp256k1 +# (hi = 0 because (-b1) * 2^256 / r fits in 128 bits for this curve) +secp_expected_endo_g1 = 0x6F547FA90ABFE4C4 | (0xE4437ED6010E8828 << 64) +secp_expected_endo_g2 = 0xE86C90E49284EB15 | (0x3086D221A7D46BCD << 64) secp_expected_endo_minus_b1 = 0x6F547FA90ABFE4C3 | (0xE4437ED6010E8828 << 64) secp_expected_endo_b2 = 0xe86c90e49284eb15 | (0x3086d221a7d46bcd << 64) @@ -558,48 +608,54 @@ def compute_splitting_constants_384(modulus, b1, b2): # ==================================================================================== -# § 13. secp256k1 Fr — THE 384-BIT SPLITTING ALGORITHM +# § 13. secp256k1 Fr — THE 256-BIT SPLITTING ALGORITHM # ==================================================================================== # -# Unlike the 256-bit variant, there is no explicit negative-k2 fix — field -# subtraction handles signs. The c1, c2 values are converted to Montgomery form -# and multiplied via field ops (which reduce mod r automatically). +# secp256k1 uses the same core computation as BN254 (compute_endomorphism_k2 in +# field_declarations.hpp), but WITHOUT the negative-k2 fix. Both k1 and k2 can +# reach 129 bits (see §0 for why); the caller handles signs. This specifically means +# that if k2 is (naively) negative, it will be returned as r - k2, a 256-bit number, +# and the caller will then detect this and handle appropriately. +# +# ALGORITHM (the `else` branch of split_into_endomorphism_scalars in +# field_declarations.hpp, for large moduli): # -# ALGORITHM (split_into_endomorphism_scalars_384 in field_declarations.hpp): +# 1. t1 = compute_endomorphism_k2(k) +# i.e. c1 = (endo_g2 · k) >> 256, c2 = (endo_g1 · k) >> 256 +# t1 = (c2·b2 - c1·(-b1)) mod r [= k2] +# 2. k2 = t1 +# 3. k1 = (t1·λ + k) mod r # -# 1. c1 = (endo_g1 · k) >> 384, c2 = (endo_g2 · k) >> 384 -# 2. r2f = c1·(-b1) - c2·b2 (cross-products, computed as field elements) -# 3. r1f = k - r2f·λ -# 4. k1 = r1f, k2 = -r2f +# No negative-k2 fix: unlike BN254, we do NOT check if t1 > 128 bits. The +# biggroup code inspects the MSB of k2 to determine its sign. -def split_scalar_384(k, modulus, lambda_val, endo_g1, endo_g2, endo_minus_b1, endo_b2): +def split_scalar_secp256k1(k, modulus, lambda_val, endo_g1, endo_g2, endo_minus_b1, endo_b2): """ - Split scalar k using the 384-bit shift variant. + Split scalar k using the 256-bit shift for secp256k1. - Implements split_into_endomorphism_scalars_384() in field_declarations.hpp. + Implements the large-modulus branch of split_into_endomorphism_scalars() + in field_declarations.hpp. Returns: - (k1, k2): The split scalars such that k ≡ k1 - λ·k2 (mod r) + (k1, k2): Full field elements such that k ≡ k1 - λ·k2 (mod r). + Both k1 and k2 can reach ~129 bits (see §0). """ input_val = k % modulus - # c1 ≈ k·b2/r, c2 ≈ k·(-b1)/r - c1 = (endo_g1 * input_val) >> 384 - c2 = (endo_g2 * input_val) >> 384 - - # Cross-products (computed as field elements in C++ via Montgomery) - c1_times_minus_b1 = (c1 * endo_minus_b1) % modulus - c2_times_b2 = (c2 * endo_b2) % modulus + # c1 = (g2 * k) >> 256, c2 = (g1 * k) >> 256 + c1 = (endo_g2 * input_val) >> 256 + c2 = (endo_g1 * input_val) >> 256 - # r2f = c1·(-b1) - c2·b2 (nearly cancels, leaving small lattice error) - r2f = (c1_times_minus_b1 - c2_times_b2) % modulus + # q1 = c1 * (-b1), q2 = c2 * b2 (raw integer multiply, low 256 bits) + q1_lo = (c1 * endo_minus_b1) & ((1 << 256) - 1) + q2_lo = (c2 * endo_b2) & ((1 << 256) - 1) - # r1f = k - r2f·λ - r1f = (input_val - r2f * lambda_val) % modulus + # t1 = (q2 - q1) mod r — this is k2 + t1 = (q2_lo - q1_lo) % modulus - # k1 = r1f, k2 = -r2f; invariant: k ≡ k1 - λ·k2 (mod r) - k1 = r1f - k2 = (-r2f) % modulus + # k1 = t1·λ + k (mod r) + k1 = (t1 * lambda_val + input_val) % modulus + k2 = t1 return k1, k2 @@ -608,8 +664,8 @@ def split_scalar_384(k, modulus, lambda_val, endo_g1, endo_g2, endo_minus_b1, en # § 14. secp256k1 Fr — SPLITTING VERIFICATION # ==================================================================================== -def verify_split_384(k, k1, k2, lambda_val, modulus): - """Verify correctness of the 384-bit scalar split.""" +def verify_split_secp256k1(k, k1, k2, lambda_val, modulus): + """Verify correctness of the secp256k1 scalar split.""" # The invariant is k ≡ k1 - λ·k2 (mod r) reconstructed = (k1 - lambda_val * k2) % modulus assert reconstructed == k % modulus, ( @@ -617,10 +673,8 @@ def verify_split_384(k, k1, k2, lambda_val, modulus): f" k1={hex(k1)}, k2={hex(k2)}\n" f" reconstructed={hex(reconstructed)}, expected={hex(k % modulus)}" ) - # For the 384-bit variant, k1 and k2 are field elements; they should be small - # enough that the decomposition is useful. We verify they fit in ~129 bits. - # (The C++ code does not explicitly truncate to 128 bits in this path; - # the values may be slightly larger than in the 256-bit path.) + # k1 and k2 are full field elements. We verify their effective magnitudes + # fit in ~129 bits (the decomposition halves the scalar bit-length). k1_eff = k1 if k1 <= modulus // 2 else modulus - k1 k2_eff = k2 if k2 <= modulus // 2 else modulus - k2 assert k1_eff.bit_length() <= 129, ( @@ -632,52 +686,95 @@ def verify_split_384(k, k1, k2, lambda_val, modulus): for k_test in [0, 1, 42, secp_lambda, secp_r - 1, secp_r // 2, secp_r // 3]: - k1, k2 = split_scalar_384( + k1, k2 = split_scalar_secp256k1( k_test, secp_r, secp_lambda, secp_endo_g1, secp_endo_g2, secp_endo_minus_b1, secp_endo_b2 ) - verify_split_384(k_test, k1, k2, secp_lambda, secp_r) - + verify_split_secp256k1(k_test, k1, k2, secp_lambda, secp_r) -# Also verify with the cube root of unity in the BASE field (secp256k1 Fq). -# The base field Fq of secp256k1 has modulus p = 2^256 - 2^32 - 977, which also -# has a cube root of unity β. This β is what gets multiplied with the x-coordinate -# in the endomorphism φ(x,y) = (β·x, y). Let's verify it. - -secp_fq_modulus = ( - 0xFFFFFFFEFFFFFC2F | - (0xFFFFFFFFFFFFFFFF << 64) | - (0xFFFFFFFFFFFFFFFF << 128) | - (0xFFFFFFFFFFFFFFFF << 192) -) +# ==================================================================================== +# § 15. SUMMARY +# ==================================================================================== +# +# Derived and verified GLV endomorphism constants for: +# - BN254 Fr (§1–§5): 254-bit, 256-bit shift, constants match bn254/fr.hpp +# - BN254 Fq (§6–§9): 254-bit, 256-bit shift, constants match bn254/fq.hpp +# - secp256k1 Fr (§10–§14): 256-bit, 256-bit shift, constants match secp256k1.hpp +# +# ALL curves use the same 256-bit shift (see §0a for the proof that this is +# sufficient for any r < 2^256). MODULUS_TOP_LIMB_LARGE_THRESHOLD (2^62) +# determines whether the 128-bit pair path (with negative-k2 fix) or the +# full-width path (no fix, caller handles signs) is used. The 128-bit path +# works for BN254 because its 254-bit modulus gives √r ~ 127 bits, leaving +# one bit of headroom below 128. For 256-bit moduli like secp256k1, √r ~ 128 +# bits leaves zero headroom: k1 exceeds 128 bits ~25% of the time (since +# |a2| = 129 bits) and k2 exceeds 128 bits ~0.3% of the time (see §16). -secp_fq_R = pow(2, 256, secp_fq_modulus) -secp_fq_R_inv = pow(secp_fq_R, -1, secp_fq_modulus) +# ==================================================================================== +# § 16. APPENDIX: 129-BIT SCALARS FOR secp256k1 +# ==================================================================================== +# +# Empirically verify the 129-bit overflow frequencies from §0 by calling +# split_scalar_secp256k1 (§13) on random inputs. -secp_fq_cube_root_montgomery = ( - 0x58a4361c8e81894e | - (0x03fde1631c4b80af << 64) | - (0xf8e98978d02e3905 << 128) | - (0x7a4a36aebcbb3d53 << 192) -) +def measure_overflow_frequency(n_samples=500_000): + """Measure the fraction of random scalars whose k1 or k2 exceeds 128 bits.""" + import random + rng = random.Random(42) -secp_fq_beta = (secp_fq_cube_root_montgomery * secp_fq_R_inv) % secp_fq_modulus -assert pow(secp_fq_beta, 3, secp_fq_modulus) == 1, "β³ ≡ 1 (mod p) for secp256k1 Fq" -assert secp_fq_beta != 1, "β must be non-trivial" + count_k1 = 0 + count_k2 = 0 + for _ in range(n_samples): + k = rng.randrange(1, secp_r) + k1, k2 = split_scalar_secp256k1( + k, secp_r, secp_lambda, + secp_endo_g1, secp_endo_g2, secp_endo_minus_b1, secp_endo_b2 + ) + if k1.bit_length() > 128: + count_k1 += 1 + if k2 <= (secp_r - (1 << 127)) and k2.bit_length() > 128: # k2 is naively in the range (-2^127, 2^129). therefore we throw away when k2 is negative. + count_k2 += 1 + pct_k1 = 100 * count_k1 / n_samples + pct_k2 = 100 * count_k2 / n_samples + print(f" k1 > 128 bits: {count_k1}/{n_samples} ({pct_k1:.1f}%)") + print(f" k2 positive and > 128 bits: {count_k2}/{n_samples} ({pct_k2:.2f}%)") + assert 20 < pct_k1 < 35, f"Expected ~25% for k1, got {pct_k1}%" + assert 0.1 < pct_k2 < 1.0, f"Expected ~0.3% for k2, got {pct_k2}%" # ==================================================================================== -# § 15. SUMMARY +# § 17. APPENDIX: WHY THE BN254 Fr AND Fq LATTICE BASES ARE NEARLY IDENTICAL # ==================================================================================== # -# Derived and verified GLV endomorphism constants for: -# - BN254 Fr (§1–§5): 254-bit, 256-bit shift, constants match bn254/fr.hpp -# - BN254 Fq (§6–§9): 254-bit, 256-bit shift, constants match bn254/fq.hpp -# - secp256k1 Fr (§10–§14): 256-bit, 384-bit shift, constants match secp256k1.hpp -# - secp256k1 Fq cube root β also verified (end of Part III) +# BN254 is parameterized by a single integer x = 0x44e992b44a6909f1 (63 bits). +# Both field primes are polynomials in x: +# +# r = 36x⁴ + 36x³ + 18x² + 6x + 1 (scalar field, Fr) +# q = 36x⁴ + 36x³ + 24x² + 6x + 1 (base field, Fq) # -# Architectural split: MODULUS_TOP_LIMB_LARGE_THRESHOLD (2^62) determines whether -# split_into_endomorphism_scalars (256-bit) or _384 (384-bit) is used. +# They differ by q − r = 6x² ≈ 2¹²⁷, tiny relative to the 254-bit primes. +# +# The GLV lattice basis vectors turn out to be simple polynomials in x: +# +# Fr basis: (a1, b1) = (2x+1, −(6x²+2x)), (a2, b2) = (6x²+4x+1, 2x+1) +# Fq basis: (a1, b1) = (2x, −(6x²+2x+1)), (a2, b2) = (6x²+4x+1, 2x ) +# +# These are verified by the determinant identities: +# +# (2x+1)² + (6x²+4x+1)(6x²+2x) = 36x⁴ + 36x³ + 18x² + 6x + 1 = r +# (2x)² + (6x²+4x+1)(6x²+2x+1) = 36x⁴ + 36x³ + 24x² + 6x + 1 = q +# +# Notice a1 = b2 for both fields (the basis matrix is "almost symmetric"), and a2 +# is IDENTICAL for both. The only difference: Fr has the +1 on a1/b2, while Fq has +# the +1 on |b1|. All four components differ by at most 1 between Fr and Fq. +# +# Reference: The BN prime parameterization is from Barreto–Naehrig (2006). The +# polynomial structure of GLV short bases for CM curves is discussed in +# B. Smith, "Easy scalar decompositions for efficient scalar multiplication +# on elliptic curves and genus 2 Jacobians" (2013), eprint.iacr.org/2013/672. +# ==================================================================================== +# § 18. Main function +# ==================================================================================== if __name__ == "__main__": print("=== Part I: BN254 Fr ===") print(f" λ (cube root): {hex(lambda_val)}") @@ -703,8 +800,7 @@ def verify_split_384(k, k1, k2, lambda_val, modulus): print(f" endo_b2: {hex(secp_endo_b2)}") print(" -> Constants match secp256k1.hpp FrParams") - print("\n=== secp256k1 Fq (base field) ===") - print(f" β (cube root): {hex(secp_fq_beta)}") - print(" -> Cube root verified") + print("\n=== Appendix: 129-bit overflow frequency (secp256k1) ===") + measure_overflow_frequency() print("\nAll verifications passed!") diff --git a/barretenberg/cpp/src/barretenberg/ecc/fields/field12.hpp b/barretenberg/cpp/src/barretenberg/ecc/fields/field12.hpp index 757c01cf464d..9fc82788945b 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/fields/field12.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/fields/field12.hpp @@ -271,7 +271,7 @@ template cl }; } - constexpr field12 from_montgomery_form() + constexpr field12 from_montgomery_form() const { return { c0.from_montgomery_form(), diff --git a/barretenberg/cpp/src/barretenberg/ecc/fields/field2_declarations.hpp b/barretenberg/cpp/src/barretenberg/ecc/fields/field2_declarations.hpp index 69a2c7ebba57..ae8c62a88f56 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/fields/field2_declarations.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/fields/field2_declarations.hpp @@ -62,17 +62,13 @@ template struct alignas(32) field2 { static constexpr field2 zero() { return field2{ base_field::zero(), base_field::zero() }; } static constexpr field2 one() { return field2{ base_field::one(), base_field::zero() }; } static constexpr field2 twist_coeff_b() { return field2{ Params::twist_coeff_b_0, Params::twist_coeff_b_1 }; } - static constexpr field2 twist_mul_by_q_x() + static constexpr field2 frobenius_on_twisted_curve_x() { - return field2{ Params::twist_mul_by_q_x_0, Params::twist_mul_by_q_x_1 }; + return field2{ Params::frobenius_on_twisted_curve_x_0, Params::frobenius_on_twisted_curve_x_1 }; } - static constexpr field2 twist_mul_by_q_y() + static constexpr field2 frobenius_on_twisted_curve_y() { - return field2{ Params::twist_mul_by_q_y_0, Params::twist_mul_by_q_y_1 }; - } - static constexpr field2 cube_root_of_unity() - { - return field2{ Params::twist_cube_root_0, Params::twist_cube_root_1 }; + return field2{ Params::frobenius_on_twisted_curve_y_0, Params::frobenius_on_twisted_curve_y_1 }; } constexpr field2 operator*(const field2& other) const noexcept; diff --git a/barretenberg/cpp/src/barretenberg/ecc/fields/field_declarations.hpp b/barretenberg/cpp/src/barretenberg/ecc/fields/field_declarations.hpp index ed47d11f06b7..5cecfce43828 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/fields/field_declarations.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/fields/field_declarations.hpp @@ -45,6 +45,11 @@ namespace bb { // // In Barretenberg, the main workhorse fields are the base and scalar fields of BN-254, which are "small" moduli: they // are each 254 bits. The field algorithms for them are constant-time. +// +// NOTE: For the 254-bit fields in Barretenberg, namely BN254 base and scalar fields, we also +// use this constexpr branching to capture another (conceptually unrelated) property: that +// the short basis of the lattice from the endomorphism is shorter than expected. See endomorphism_scalars.py for more +// information. static constexpr uint64_t MODULUS_TOP_LIMB_LARGE_THRESHOLD = 0x4000000000000000ULL; // 2^62 /** @@ -273,64 +278,21 @@ template struct alignas(32) field { static constexpr field neg_one() { return -field(1); } static constexpr field one() { return field(1); } - static constexpr field external_coset_generator() - { -#if defined(__SIZEOF_INT128__) && !defined(__wasm__) - const field result{ - Params::coset_generators_0[7], - Params::coset_generators_1[7], - Params::coset_generators_2[7], - Params::coset_generators_3[7], - }; -#else - const field result{ - Params::coset_generators_wasm_0[7], - Params::coset_generators_wasm_1[7], - Params::coset_generators_wasm_2[7], - Params::coset_generators_wasm_3[7], - }; -#endif - - return result; - } - - static constexpr field tag_coset_generator() - { -#if defined(__SIZEOF_INT128__) && !defined(__wasm__) - const field result{ - Params::coset_generators_0[6], - Params::coset_generators_1[6], - Params::coset_generators_2[6], - Params::coset_generators_3[6], - }; -#else - const field result{ - Params::coset_generators_wasm_0[6], - Params::coset_generators_wasm_1[6], - Params::coset_generators_wasm_2[6], - Params::coset_generators_wasm_3[6], - }; -#endif - - return result; - } - - template static constexpr field coset_generator() + static constexpr field coset_generator() { - static_assert(idx < 7); #if defined(__SIZEOF_INT128__) && !defined(__wasm__) const field result{ - Params::coset_generators_0[idx], - Params::coset_generators_1[idx], - Params::coset_generators_2[idx], - Params::coset_generators_3[idx], + Params::coset_generator_0, + Params::coset_generator_1, + Params::coset_generator_2, + Params::coset_generator_3, }; #else const field result{ - Params::coset_generators_wasm_0[idx], - Params::coset_generators_wasm_1[idx], - Params::coset_generators_wasm_2[idx], - Params::coset_generators_wasm_3[idx], + Params::coset_generator_0, + Params::coset_generator_1, + Params::coset_generator_2, + Params::coset_generator_3, }; #endif @@ -466,71 +428,92 @@ template struct alignas(32) field { * We pre-compute scalars g1 = (2^256 * b1) / n, g2 = (2^256 * b2) / n, to avoid having to perform long division * on 512-bit scalars **/ - static void split_into_endomorphism_scalars(const field& k, field& k1, field& k2) - { - // if the modulus is a >= 255-bit integer, we need to use a basis where g1, g2 have been shifted by 2^384 - if constexpr (Params::modulus_3 >= MODULUS_TOP_LIMB_LARGE_THRESHOLD) { - split_into_endomorphism_scalars_384(k, k1, k2); - } else { - std::pair, std::array> ret = split_into_endomorphism_scalars(k); - k1.data[0] = ret.first[0]; - k1.data[1] = ret.first[1]; - -#if !defined(__clang__) && defined(__GNUC__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Warray-bounds" -#endif - k2.data[0] = ret.second[0]; // NOLINT - k2.data[1] = ret.second[1]; -#if !defined(__clang__) && defined(__GNUC__) -#pragma GCC diagnostic pop -#endif - } - } - - // NOTE: this form is only usable if the modulus is 254 bits or less, otherwise see - // split_into_endomorphism_scalars_384. - // DOES NOT assume that the input is reduced; it can be in coarse form. - // TODO(https://github.com/AztecProtocol/barretenberg/issues/851): Unify these APIs. - static std::pair, std::array> split_into_endomorphism_scalars(const field& k) + /** + * @brief Shared core of the endomorphism scalar decomposition. + * + * Computes k2 = round(b2·k/r)·(-b1) + round((-b1)·k/r)·b2, using the + * 256-bit-shift approximation g = floor(b·2^256/r) for both BN254 and + * secp256k1. See endomorphism_scalars.py §0 for the proof that the + * approximation error is bounded to {0, -1} for any r < 2^256. + * + * The result is a raw (non-Montgomery) `field` whose low 128-or-129 bits + * hold k2. This function will be called in either the BN254 base/scalar field + * or the generic, secp256k1 branch. + */ + static field compute_endomorphism_k2(const field& k) { - static_assert(Params::modulus_3 < MODULUS_TOP_LIMB_LARGE_THRESHOLD); + // force into strict form. field input = k.reduce_once(); constexpr field endo_g1 = { Params::endo_g1_lo, Params::endo_g1_mid, Params::endo_g1_hi, 0 }; - constexpr field endo_g2 = { Params::endo_g2_lo, Params::endo_g2_mid, 0, 0 }; - constexpr field endo_minus_b1 = { Params::endo_minus_b1_lo, Params::endo_minus_b1_mid, 0, 0 }; - constexpr field endo_b2 = { Params::endo_b2_lo, Params::endo_b2_mid, 0, 0 }; - // compute c1 = (g2 * k) >> 256 + // c1 = (g2 * k) >> 256, c2 = (g1 * k) >> 256 wide_array c1 = endo_g2.mul_512(input); - // compute c2 = (g1 * k) >> 256 wide_array c2 = endo_g1.mul_512(input); - // (the bit shifts are implicit, as we only utilize the high limbs of c1, c2 + // extract high halves + field c1_hi{ c1.data[4], c1.data[5], c1.data[6], c1.data[7] }; + field c2_hi{ c2.data[4], c2.data[5], c2.data[6], c2.data[7] }; - field c1_hi = { - c1.data[4], c1.data[5], c1.data[6], c1.data[7] - }; // *(field*)((uintptr_t)(&c1) + (4 * sizeof(uint64_t))); - field c2_hi = { - c2.data[4], c2.data[5], c2.data[6], c2.data[7] - }; // *(field*)((uintptr_t)(&c2) + (4 * sizeof(uint64_t))); - - // compute q1 = c1 * -b1 + // q1 = c1 * (-b1), q2 = c2 * b2 wide_array q1 = c1_hi.mul_512(endo_minus_b1); - // compute q2 = c2 * b2 wide_array q2 = c2_hi.mul_512(endo_b2); - // FIX: Avoid using 512-bit multiplication as its not necessary. - // c1_hi, c2_hi can be uint256_t's and the final result (without montgomery reduction) - // could be casted to a field. field q1_lo{ q1.data[0], q1.data[1], q1.data[2], q1.data[3] }; field q2_lo{ q2.data[0], q2.data[1], q2.data[2], q2.data[3] }; - field t1 = (q2_lo - q1_lo).reduce_once(); + return (q2_lo - q1_lo).reduce_once(); + } + + /** + * @brief Full-width endomorphism decomposition: k ≡ k1 - k2·λ (mod r). + * Modifies the field elements k1 and k2. + * + * For BN254 base/scalar fields, delegates to the 128-bit pair + * overload, which applies the negative-k2 fix. Returns k1, k2 in the low + * 2 limbs (upper limbs zeroed). Both fit in 128 bits. + * + * For generic 256-bit fields: returns k1, k2 as full field elements + * elements (non-Montgomery). k1 fits in ~128 bits; k2 fits in ~129 bits. + * No negative-k2 fix — the caller (biggroup_nafs.hpp) handles signs by + * inspecting the MSB of k2. + */ + static void split_into_endomorphism_scalars(const field& k, field& k1, field& k2) + { + if constexpr (Params::modulus_3 < MODULUS_TOP_LIMB_LARGE_THRESHOLD) { + // BN254 base or scalar field: use path that corresponds to 128-bit outputs. + auto ret = split_into_endomorphism_scalars(k); + k1 = { ret.first[0], ret.first[1], 0, 0 }; + k2 = { ret.second[0], ret.second[1], 0, 0 }; + } else { + // Large modulus (secp256k1): full-width path. + field t1 = compute_endomorphism_k2(k); + k2 = t1; + k1 = ((t1 * cube_root_of_unity()) + k).reduce_once(); + } + } + + /** + * @brief 128-bit endomorphism decomposition: k ≡ k1 - k2·λ (mod r). + * + * Returns { {k1_lo, k1_hi}, {k2_lo, k2_hi} } — each scalar as a pair of + * uint64_t representing its low 128 bits. Both k1 and k2 are guaranteed to + * fit in 128 bits (the negative-k2 fix ensures this for the ~2^{-64} of + * inputs where k2 would otherwise be slightly negative). + * + * Only valid for fields such that the splitting_scalars algorithm produces 128 bit outputs. In Barretenberg, these + * are just the base and scalar fields of BN254. These are the only "small modulus" fields, so we use a static + * assert to force this. + * + * Does NOT assume that the input is reduced + */ + static std::pair, std::array> split_into_endomorphism_scalars(const field& k) + { + static_assert(Params::modulus_3 < MODULUS_TOP_LIMB_LARGE_THRESHOLD); + field t1 = compute_endomorphism_k2(k); // k2 (= t1) can be slightly negative for ~2^{-64} of inputs. // When negative, t1 = k2 + r is 254 bits (upper limbs nonzero). @@ -538,67 +521,17 @@ template struct alignas(32) field { // This shifts k2 by +|b1| (~127 bits, now positive) and k1 by -a1 (~64 bits), // keeping both within 128 bits. See endomorphism_scalars.py for more details. if (t1.data[2] != 0 || t1.data[3] != 0) { + constexpr field endo_minus_b1 = { Params::endo_minus_b1_lo, Params::endo_minus_b1_mid, 0, 0 }; t1 = (t1 + endo_minus_b1).reduce_once(); } - field beta = cube_root_of_unity(); - field t2 = (t1 * beta + input).reduce_once(); + field t2 = ((t1 * cube_root_of_unity()) + k).reduce_once(); return { { t2.data[0], t2.data[1] }, { t1.data[0], t1.data[1] }, }; } - static void split_into_endomorphism_scalars_384(const field& input, field& k1_out, field& k2_out) - { - constexpr field minus_b1f{ - Params::endo_minus_b1_lo, - Params::endo_minus_b1_mid, - 0, - 0, - }; - constexpr field b2f{ - Params::endo_b2_lo, - Params::endo_b2_mid, - 0, - 0, - }; - constexpr uint256_t g1{ - Params::endo_g1_lo, - Params::endo_g1_mid, - Params::endo_g1_hi, - Params::endo_g1_hihi, - }; - constexpr uint256_t g2{ - Params::endo_g2_lo, - Params::endo_g2_mid, - Params::endo_g2_hi, - Params::endo_g2_hihi, - }; - - field kf = input.reduce_once(); - uint256_t k{ kf.data[0], kf.data[1], kf.data[2], kf.data[3] }; - - uint512_t c1 = (uint512_t(k) * static_cast(g1)) >> 384; - uint512_t c2 = (uint512_t(k) * static_cast(g2)) >> 384; - - field c1f{ c1.lo.data[0], c1.lo.data[1], c1.lo.data[2], c1.lo.data[3] }; - field c2f{ c2.lo.data[0], c2.lo.data[1], c2.lo.data[2], c2.lo.data[3] }; - - c1f.self_to_montgomery_form(); - c2f.self_to_montgomery_form(); - c1f = c1f * minus_b1f; - c2f = c2f * b2f; - field r2f = c1f - c2f; - field beta = cube_root_of_unity(); - field r1f = input.reduce_once() - r2f * beta; - k1_out = r1f; - k2_out = -r2f; - } - - // static constexpr auto coset_generators = compute_coset_generators(); - // static constexpr std::array coset_generators = compute_coset_generators((1 << 30U)); - friend std::ostream& operator<<(std::ostream& os, const field& a) { field out = a.from_montgomery_form_reduced(); @@ -750,7 +683,6 @@ template struct alignas(32) field { BB_INLINE static void asm_self_reduce_once(const field& a) noexcept; static constexpr uint64_t zero_reference = 0x00ULL; #endif - static constexpr size_t COSET_GENERATOR_SIZE = 15; constexpr field tonelli_shanks_sqrt() const noexcept; static constexpr size_t primitive_root_log_size() noexcept; diff --git a/barretenberg/cpp/src/barretenberg/ecc/fields/field_docs.md b/barretenberg/cpp/src/barretenberg/ecc/fields/field_docs.md index f28a4e32510e..4fee2327dc28 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/fields/field_docs.md +++ b/barretenberg/cpp/src/barretenberg/ecc/fields/field_docs.md @@ -1,45 +1,45 @@ Prime field documentation {#field_docs} === -Barretenberg has its own implementation of finite field arithmetic. The implementation targets 254-bit (bn254, grumpkin) and 256-bit (secp256k1, secp256r1) fields. Internally the field is represented as a little-endian C-array of 4 uint64_t limbs. For 254-bit fields, the internal representation must be in the range \f$[0, 2p)\f$ (which we refer to as the _coarse representation_), while for 256-bit fields the internal representation is an arbitrary `uint256_t`. +Barretenberg has its own implementation of finite field arithmetic. The implementation targets 254-bit (bn254, grumpkin) and 256-bit (secp256k1, secp256r1) fields. Internally the field is represented as a little-endian C-array of 4 uint64_t limbs. For 254-bit fields, the internal representation must be in the range $[0, 2p)$ (which we refer to as the _coarse representation_), while for 256-bit fields the internal representation is an arbitrary `uint256_t`. ## Field arithmetic ### Introduction to Montgomery form {#field_docs_montgomery_explainer} -We use Montgomery multiplication to speed up field multiplication. For an original element \f$ a \in \mathbb F_p\f$ the element is represented internally as $$ a⋅R\ mod\ p$$ where \f$R = 2^d\ mod\ p\f$. The chosen \f$d\f$ depends on the build configuration: -1. \f$d=29⋅9=261\f$ for builds that don't support the `uint128_t` type, for example, for WASM build -2. \f$d=64⋅4=256\f$ for standard builds (x86_64). +We use Montgomery multiplication to speed up field multiplication. For an original element $ a \in \mathbb F_p$ the element is represented internally as $$ a⋅R\ mod\ p$$ where $R = 2^d\ mod\ p$. The chosen $d$ depends on the build configuration: +1. $d=29⋅9=261$ for builds that don't support the `uint128_t` type, for example, for WASM build +2. $d=64⋅4=256$ for standard builds (x86_64). -The goal of using Montgomery form is to avoid heavy division modulo \f$p\f$. To compute a representative of element $$c = a⋅b\ mod\ p$$ we compute $$c⋅R = (a⋅R)⋅(b⋅R) / R\ mod\ p,$$ but we use an efficient division trick to avoid the naive modular division. Let's look into the standard 4⋅64 case: +The goal of using Montgomery form is to avoid heavy division modulo $p$. To compute a representative of element $$c = a⋅b\ mod\ p$$ we compute $$c⋅R = (a⋅R)⋅(b⋅R) / R\ mod\ p,$$ but we use an efficient division trick to avoid the naive modular division. Let's look into the standard 4⋅64 case: 1. First, we compute the value $$c_r=c⋅R⋅R = aR⋅bR$$ in integers and get a value with 8 64-bit limbs -2. Then we take the lowest limb of \f$c_r\f$ (i.e., \f$c_r[0]\f$) and multiply it by a special _precomputed_ value $$r_{inv} = -1 ⋅ p^{-1}\ mod\ 2^{64}$$ As a result we get $$k = r_{inv}⋅ c_r[0]\ mod\ 2^{64}$$ -3. Next we update \f$c_r\f$ in integers by adding \f$k⋅p\f$: $$c_r += k⋅p$$ You might notice that the value of \f$c_r\ mod\ p\f$ hasn't changed, since we've added a multiple of the modulus. At the same time, if we look at the expression modulo \f$2^{64}\f$: $$c_r + k⋅p = c_r + c_r⋅r_{inv}⋅p = c_r + c_r⋅ (-1)⋅p^{-1}⋅p = c_r - c_r = 0\ mod\ 2^{64}.$$ The result is equivalent modulo \f$p\f$, but we zeroed out the lowest limb -4. We perform the same operation for \f$c_r[1]\f$, but instead of adding \f$k⋅p\f$, we add \f$2^{64}⋅k⋅p\f$. In the implementation, instead of adding \f$k⋅ p\f$ to limbs of \f$c_r\f$ starting with zero, we just start with limb 1. This ensures that \f$c_r[1]=0\f$. We then perform the same operation for 2 more limbs. -5. At this stage the array \f$c_r\f$ has the property that the first 4 limbs of the total 8 limbs are zero. So if we treat the 4 high limbs as a separate integer \f$c_{r.high}\f$, $$c_r = c_{r.high}⋅2^{256}=c_{r.high}⋅R\ mod\ p \Rightarrow c_{r.high} = c\cdot R\ mod\ p$$ and we can get the evaluation simply by taking the 4 high limbs of \f$c_r\f$. -6. The previous step has reduced the intermediate value of \f$cR\f$ to range \f$[0,2p)\f$, so we must check if it is more than \f$p\f$ and subtract the modulus once if it overflows. +2. Then we take the lowest limb of $c_r$ (i.e., $c_r[0]$) and multiply it by a special _precomputed_ value $$r_{inv} = -1 ⋅ p^{-1}\ mod\ 2^{64}$$ As a result we get $$k = r_{inv}⋅ c_r[0]\ mod\ 2^{64}$$ +3. Next we update $c_r$ in integers by adding $k⋅p$: $$c_r += k⋅p$$ You might notice that the value of $c_r\ mod\ p$ hasn't changed, since we've added a multiple of the modulus. At the same time, if we look at the expression modulo $2^{64}$: $$c_r + k⋅p = c_r + c_r⋅r_{inv}⋅p = c_r + c_r⋅ (-1)⋅p^{-1}⋅p = c_r - c_r = 0\ mod\ 2^{64}.$$ The result is equivalent modulo $p$, but we zeroed out the lowest limb +4. We perform the same operation for $c_r[1]$, but instead of adding $k⋅p$, we add $2^{64}⋅k⋅p$. In the implementation, instead of adding $k⋅ p$ to limbs of $c_r$ starting with zero, we just start with limb 1. This ensures that $c_r[1]=0$. We then perform the same operation for 2 more limbs. +5. At this stage the array $c_r$ has the property that the first 4 limbs of the total 8 limbs are zero. So if we treat the 4 high limbs as a separate integer $c_{r.high}$, $$c_r = c_{r.high}⋅2^{256}=c_{r.high}⋅R\ mod\ p \Rightarrow c_{r.high} = c\cdot R\ mod\ p$$ and we can get the evaluation simply by taking the 4 high limbs of $c_r$. +6. The previous step has reduced the intermediate value of $cR$ to range $[0,2p)$, so we must check if it is more than $p$ and subtract the modulus once if it overflows. -On a high level, what we are doing is iteratively adding a multiple of \f$p\f$ until the current bottom limb is zero, then shifting by a limb (amounting to dividing by \f$2^64\f$). +On a high level, what we are doing is iteratively adding a multiple of $p$ until the current bottom limb is zero, then shifting by a limb (amounting to dividing by $2^{64}$). #### Bounds analysis Why does this work? We present several versions of the analysis, for completeness. -* Suppose both \f$aR\f$ and \f$bR\f$ are less than the modulus \f$p\f$ in integers, so $$aR\cdot bR <= (p-1)^2.$$ During each of the \f$k\cdot p\f$ addition rounds we can add at most \f$(2^{64}-1)p\f$ to the corresponding digits, so at most we add \f$(2^{256}-1)p\f$ and the total is $$aR\cdot bR + k_{0,1,2,3}p \le (p-1)^2+(2^{256}-1)p < 2\cdot 2^{256}p \Rightarrow c_{r.high} = \frac{aR\cdot bR + k_{0,1,2,3}p}{2^{256}} < 2p.$$ +* Suppose both $aR$ and $bR$ are less than the modulus $p$ in integers, so $$aR\cdot bR <= (p-1)^2.$$ During each of the $k\cdot p$ addition rounds we can add at most $(2^{64}-1)p$ to the corresponding digits, so at most we add $(2^{256}-1)p$ and the total is $$aR\cdot bR + k_{0,1,2,3}p \le (p-1)^2+(2^{256}-1)p < 2\cdot 2^{256}p \Rightarrow c_{r.high} = \frac{aR\cdot bR + k_{0,1,2,3}p}{2^{256}} < 2p.$$ -* For our 256-bit fields, we _cannot_ assume that \f$aR\f$ and \f$bR\f$ are less than the modulus \f$p\f$; we simply know that they are 256-bit numbers. Nonetheless, the same analysis shows that the output is less than \f$2^{256} + p -1\f$. This means that (conditionally) subtracting one copy of \f$p\f$ is enough to get us to the valid range of \f$[0, 2^{256})\f$. +* For our 256-bit fields, we _cannot_ assume that $aR$ and $bR$ are less than the modulus $p$; we simply know that they are 256-bit numbers. Nonetheless, the same analysis shows that the output is less than $2^{256} + p -1$. This means that (conditionally) subtracting one copy of $p$ is enough to get us to the valid range of $[0, 2^{256})$. -* For 254-bit fields (e.g. the BN-254 base and scalar fields) we can do even better by employing a simple trick. Note that 4 64-bit limbs allow 256 bits of storage. We relax the internal representation to use values in range \f$[0,2p)\f$. The addition, negation and subtraction operation logic doesn't change, we simply replace the modulus \f$p\f$ with \f$2p\f$, but the multiplication becomes more efficient. The multiplicands are in range \f$[0,2p)\f$, but we add multiples of modulus \f$p\f$ to reduce limbs, not \f$2p\f$. If we revisit the \f$c_r\f$ formula: +* For 254-bit fields (e.g. the BN-254 base and scalar fields) we can do even better by employing a simple trick. Note that 4 64-bit limbs allow 256 bits of storage. We relax the internal representation to use values in range $[0,2p)$. The addition, negation and subtraction operation logic doesn't change, we simply replace the modulus $p$ with $2p$, but the multiplication becomes more efficient. The multiplicands are in range $[0,2p)$, but we add multiples of modulus $p$ to reduce limbs, not $2p$. If we revisit the $c_r$ formula: $$aR\cdot bR + k_{0,1,2,3}p \le (2p-1)^2+(2^{256}-1)p = 2^{256}p+4p^2-5p+1 \Rightarrow$$ $$\Rightarrow c_{r.high} = \frac{aR\cdot bR + k_{0,1,2,3}p}{2^{256}} \le \frac{2^{256}p+4p^2-5p+1}{2^{256}}=p +\frac{4p^2 - 5p +1}{2^{256}}, 4p < 2^{256} \Rightarrow$$ $$\Rightarrow p +\frac{4p^2 - 5p +1}{2^{256}} < 2p$$ So we ended in the same range and we don't have to perform additional reductions. -**N.B.** In the code we refer to this form, when the limbs are only constrained to be in the range \f$[0,2p)\f$, as the coarse-representation. +**N.B.** In the code we refer to this form, when the limbs are only constrained to be in the range $[0,2p)$, as the coarse-representation. ### Yuval reduction For our 254-bit multiplication in WASM, we use a reduction technique found by Yuval. For a reference, please see this [hackmd](https://hackmd.io/@Ingonyama/Barret-Montgomery). -Recall that in standard Montgomery reduction, we zero out the lowest limb by adding a carefully chosen multiple of the modulus \f$p\f$. In particular, if we were to use standard Montgomery reduction given our limb-decomposition for WASM: given an accumulator \f$x = \sum_{i=0}^{n} \text{result}_i \cdot 2^{29i}\f$, we compute \f$k = \text{result}_0 \cdot (-p^{-1}) \mod 2^{29}\f$ and add \f$k \cdot p\f$ to \f$x\f$. This makes the lowest 29 bits zero (since \f$\text{result}_0 + k \cdot p_0 \equiv 0 \mod 2^{29}\f$), allowing us to "shift right" by discarding the zeroed limb. +Recall that in standard Montgomery reduction, we zero out the lowest limb by adding a carefully chosen multiple of the modulus $p$. In particular, if we were to use standard Montgomery reduction given our limb-decomposition for WASM: given an accumulator $x = \sum_{i=0}^{n} \text{result}_i \cdot 2^{29i}$, we compute $k = \text{result}_0 \cdot (-p^{-1}) \mod 2^{29}$ and add $k \cdot p$ to $x$. This makes the lowest 29 bits zero (since $\text{result}_0 + k \cdot p_0 \equiv 0 \mod 2^{29}$), allowing us to "shift right" by discarding the zeroed limb. -Yuval's method takes a different approach. Instead of adding a multiple of \f$p\f$ to zero out the low bits, we directly compute the equivalent value after the divide by \f$2^{29}\f$ step. Given the same accumulator \f$x\f$, we want to find \f$x / 2^{29} \mod p\f$. We can rewrite this as: +Yuval's method takes a different approach. Instead of adding a multiple of $p$ to zero out the low bits, we directly compute the equivalent value after the divide by $2^{29}$ step. Given the same accumulator $x$, we want to find $x / 2^{29} \mod p$. We can rewrite this as: $$x / 2^{29} = (x - \text{result}_0) / 2^{29} + \text{result}_0 / 2^{29} \mod p.$$ -The first term \f$(x - \text{result}_0) / 2^{29}\f$ is simply the higher limbs shifted down. The second term requires computing \f$\text{result}_0 \cdot 2^{-29} \mod p\f$, which we precompute as `r_inv_wasm` (stored in 9 limbs). +The first term $(x - \text{result}_0) / 2^{29}$ is simply the higher limbs shifted down. The second term requires computing $\text{result}_0 \cdot 2^{-29} \mod p$, which we precompute as `r_inv_wasm` (stored in 9 limbs). -So instead of computing \f$k = \text{result}_0 \cdot (-p^{-1})\f$ and adding \f$k \cdot p\f$ (9 multiply-accumulates), we compute \f$\text{result}_0 \cdot r\_inv\_wasm\f$ and add it to the higher limbs (also 9 multiply-accumulates). The key insight is that both approaches require the same number of operations, but Yuval's method avoids the need for a separate "zero out and shift" step—the shift is implicit in how we interpret the result. +So instead of computing $k = \text{result}_0 \cdot (-p^{-1})$ and adding $k \cdot p$ (9 multiply-accumulates), we compute $\text{result}_0 \cdot r\_inv\_wasm$ and add it to the higher limbs (also 9 multiply-accumulates). The key insight is that both approaches require the same number of operations, but Yuval's method avoids the need for a separate "zero out and shift" step—the shift is implicit in how we interpret the result. In code, `wasm_reduce_yuval` implements this as: ```cpp @@ -52,41 +52,41 @@ The term `(result_0 >> 29)` handles any overflow bits in `result_0` beyond the l #### Structure of WASM Montgomery multiplication -In 254-bit WASM multiplication, the full Montgomery reduction requires 9 limb-reductions (to divide by \f$2^{261} = 2^{29 \cdot 9}\f$). **We apply Yuval's method for the first 8 reductions, and standard Montgomery reduction for the 9th (final) reduction.** +In 254-bit WASM multiplication, the full Montgomery reduction requires 9 limb-reductions (to divide by $2^{261} = 2^{29 \cdot 9}$). **We apply Yuval's method for the first 8 reductions, and standard Montgomery reduction for the 9th (final) reduction.** Why not use Yuval for all 9? The key issue is that Yuval's method takes a 10-limb input and produces a 10-limb output (the reduced value spans 9 limbs, shifted up by one position). If we used Yuval for the 9th reduction, we would end up with a 10-limb result instead of the desired 9-limb result. The standard Montgomery reduction (`wasm_reduce`), by contrast, takes 9 limbs and produces 9 limbs (with the lowest limb zeroed and discardable), giving us exactly the 9-limb output we need. #### Bounds analysis -We must verify that the output is in \f$[0, 2p)\f$ (the coarse representation) without requiring an additional subtraction of \f$p\f$. +We must verify that the output is in $[0, 2p)$ (the coarse representation) without requiring an additional subtraction of $p$. -After the 9 multiply-adds, we have \f$aR \cdot bR\f$ stored across 17 limbs. Since both \f$aR\f$ and \f$bR\f$ are in \f$[0, 2p)\f$, this product is at most \f$4p^2\f$. +After the 9 multiply-adds, we have $aR \cdot bR$ stored across 17 limbs. Since both $aR$ and $bR$ are in $[0, 2p)$, this product is at most $4p^2$. After 8 Yuval reductions and 1 standard reduction, we have computed: $$\frac{aR \cdot bR + k_0 \cdot r_{inv} + k_1 \cdot r_{inv} + \cdots + k_7 \cdot r_{inv} + k_8 \cdot p}{2^{261}}$$ -where each \f$k_i\f$ is the masked low 29 bits at reduction step \f$i\f$. By construction: -- \f$k_0 < 2^{29}\f$ -- \f$k_1 < 2^{58}\f$ (since it includes carries from the previous step) -- For $i < 8$, we have \f$k_i < 2^{29(i+1)}\f$ -- The sum \f$\sum_{i=0}^{7} k_i < 2^{232}\f$ (geometric series) -- \f$k_8 < 2^{261} - 2^{232}\f$ +where each $k_i$ is the masked low 29 bits at reduction step $i$. By construction: +- $k_0 < 2^{29}$ +- $k_1 < 2^{58}$ (since it includes carries from the previous step) +- For $i < 8$, we have $k_i < 2^{29(i+1)}$ +- The sum $\sum_{i=0}^{7} k_i < 2^{232}$ (geometric series) +- $k_8 < 2^{261} - 2^{232}$ -Since \f$r_{inv} = 2^{-29} \mod p < p\f$, the total added via Yuval reductions is bounded by \f$(2^{232} - 1) \cdot p\f$. The final standard reduction adds at most \f$(2^{261} - 2^{232}) \cdot p\f$. +Since $r_{inv} = 2^{-29} \mod p < p$, the total added via Yuval reductions is bounded by $(2^{232} - 1) \cdot p$. The final standard reduction adds at most $(2^{261} - 2^{232}) \cdot p$. Therefore, the numerator is bounded by: $$4p^2 + (2^{232} - 1) \cdot p + (2^{261} - 2^{232}) \cdot p < 4p^2 + 2^{261} \cdot p$$ -Dividing by \f$2^{261}\f$: +Dividing by $2^{261}$: $$\frac{4p^2 + 2^{261} \cdot p}{2^{261}} = p + \frac{4p^2}{2^{261}}$$ -For 254-bit primes, \f$p < 2^{254}\f$, so \f$4p^2 < 4 \cdot 2^{508} = 2^{510}\f$, and: +For 254-bit primes, $p < 2^{254}$, so $4p^2 < 4 \cdot 2^{508} = 2^{510}$, and: $$\frac{4p^2}{2^{261}} < 1 $$ -Thus the result is less than \f$p + 1\f$, which is of course in the coarse representation range \f$[0, 2p)\f$. No additional reduction is required. +Thus the result is less than $p + 1$, which is of course in the coarse representation range $[0, 2p)$. No additional reduction is required. ### Converting to and from Montgomery form -Obviously we want to avoid using standard form division when converting between forms, so we use Montgomery form to convert to Montgomery form. If we look at a value \f$a\ mod\ p\f$ we can notice that this is the Montgomery form of \f$a\cdot R^{-1}\ mod\ p\f$, so if we want to get \f$aR\f$ from it, we need to multiply it by the Montgomery form of \f$R\ mod\ p\f$, which is \f$R\cdot R\ mod\ p\f$. So using Montgomery multiplication we compute +Obviously we want to avoid using standard form division when converting between forms, so we use Montgomery form to convert to Montgomery form. If we look at a value $a\ mod\ p$ we can notice that this is the Montgomery form of $a\cdot R^{-1}\ mod\ p$, so if we want to get $aR$ from it, we need to multiply it by the Montgomery form of $R\ mod\ p$, which is $R\cdot R\ mod\ p$. So using Montgomery multiplication we compute $$a \cdot R^2 / R = a\cdot R\ mod\ p$$ @@ -131,11 +131,11 @@ Most of the time field is used with uint64_t or uint256_t in our codebase, but t Conversion from field elements exists only to unsigned integers and bools. The value is converted from montgomery and appropriate number of lowest bits is used to initialize the value. -**N.B.** Functions for converting from uint256_t and back are not bijective, since values \f$ \ge p\f$ will be reduced. +**N.B.** Functions for converting from uint256_t and back are not bijective, since values $ \ge p$ will be reduced. ## Field parameters -The field template is instantiated with field parameter classes, for example, class bb::Bn254FqParams. Each such class contains at least the modulus (in 64-bit and 29-bit form), r_inv (used to efficient reductions) and 2 versions of r_squared used for converting to Montgomery form (64-bit and WASM/29-bit version). r_squared and other parameters (such as cube_root, primitive_root and coset_generators) are defined for wasm separately, because the values represent an element already in Montgomery form. +The field template is instantiated with field parameter classes, for example, class bb::Bn254FqParams. Each such class contains at least the modulus (in 64-bit and 29-bit form), r_inv (used to efficient reductions) and 2 versions of r_squared used for converting to Montgomery form (64-bit and WASM/29-bit version). r_squared and other parameters (such as cube_root, primitive_root and coset_generator) are defined for wasm separately, because the values represent an element already in Montgomery form. ## Helpful python snippets diff --git a/barretenberg/cpp/src/barretenberg/ecc/fields/field_impl.hpp b/barretenberg/cpp/src/barretenberg/ecc/fields/field_impl.hpp index 238516e480bf..640da1d26f6f 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/fields/field_impl.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/fields/field_impl.hpp @@ -520,12 +520,12 @@ template constexpr field field::tonelli_shanks_sqrt() const noex // ----------------------------------------------------------------------------------------- // STEP 3: Set up precomputed lookup tables for the discrete log computation // ----------------------------------------------------------------------------------------- - // g = r^Q where r is a quadratic non-residue (coset_generator<0>). + // g = r^Q where r is a quadratic non-residue (coset_generator). // Since r has order (p-1) and Q is the odd part, g has order exactly 2^S. - constexpr field g = coset_generator<0>().pow(Q); + constexpr field g = coset_generator().pow(Q); // g_inv = g^{-1} = r^{-Q} = r^{p-1-Q} - constexpr field g_inv = coset_generator<0>().pow(modulus - 1 - Q); + constexpr field g_inv = coset_generator().pow(modulus - 1 - Q); // S = primitive_root_log_size() is the 2-adic valuation of (p-1), i.e., the largest power of 2 dividing (p-1). constexpr size_t root_bits = primitive_root_log_size(); diff --git a/barretenberg/cpp/src/barretenberg/ecc/fields/general_field.test.cpp b/barretenberg/cpp/src/barretenberg/ecc/fields/general_field.test.cpp index b318cbd19676..32e33d1177df 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/fields/general_field.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ecc/fields/general_field.test.cpp @@ -6,6 +6,7 @@ * @note: Prime-field-specific tests are in prime_field.test.cpp */ +#include "barretenberg/common/type_traits.hpp" #include "barretenberg/ecc/curves/bn254/fq.hpp" #include "barretenberg/ecc/curves/bn254/fq12.hpp" #include "barretenberg/ecc/curves/bn254/fq2.hpp" @@ -20,6 +21,15 @@ using namespace bb; template class FieldTest : public ::testing::Test {}; +template +concept HasPow = IsAnyOf; + +template +concept HasSqrt = IsAnyOf; + +template +concept IsExtensionField = IsAnyOf; + using AllFieldTypes = ::testing:: Types; @@ -245,6 +255,39 @@ TYPED_TEST(FieldTest, DoubleInverse) EXPECT_EQ(a_inv_inv, a); } +// ================================ +// Exponentation +// ================================ + +TYPED_TEST(FieldTest, PowRegressionCheck) +{ + if constexpr (HasPow) { + using FF = TypeParam; + + FF zero = FF::zero(); + FF one = FF::one(); + + EXPECT_EQ(zero.pow(uint256_t(0)), one); + } +} + +TYPED_TEST(FieldTest, Sqrt) +{ + if constexpr (HasSqrt) { + using FF = TypeParam; + + FF input = FF::random_element(); + auto [is_sqr, root] = input.sqrt(); + FF result = root.sqr(); + + if (is_sqr) { + EXPECT_EQ(result, input); + } else { + EXPECT_EQ(result, FF::zero()); + } + } +} + // ================================ // Self-Modifying Operations // ================================ @@ -341,3 +384,204 @@ TYPED_TEST(FieldTest, SubMulConsistency) EXPECT_EQ(result, expected); } + +TYPED_TEST(FieldTest, MulSqrConsistency) +{ + using FF = TypeParam; + + // Check that (a - b) * (a + b) = a^2 - b^2 + FF a = FF::random_element(); + FF b = FF::random_element(); + FF t1; + FF t2; + FF mul_result; + FF sqr_result; + + t1 = a - b; + t2 = a + b; + mul_result = t1 * t2; + + t1 = a.sqr(); + t2 = b.sqr(); + sqr_result = t1 - t2; + + EXPECT_EQ(mul_result, sqr_result); +} + +// ================================ +// Montgomery Form and Reduction +// ================================ + +TYPED_TEST(FieldTest, FromMontgomeryForm) +{ + using FF = TypeParam; + + constexpr FF t0 = FF::one(); + // Use from_montgomery_form_reduced() for base fields to ensure full reduction to [0, p). + // The WASM Montgomery multiplication returns coarse results in [0, 2p), so without + // reduce_once() the raw limbs may hold p+1 instead of 1. + // Extension fields already call from_montgomery_form_reduced() on their components internally. + constexpr FF result = [&]() constexpr { + if constexpr (!IsExtensionField) { + return t0.from_montgomery_form_reduced(); + } else { + return t0.from_montgomery_form(); + } + }(); + constexpr uint256_t expected = 0x01; + uint256_t to_be_compared; + + if constexpr (!IsExtensionField) { + to_be_compared = { result.data[0], result.data[1], result.data[2], result.data[3] }; + } else if constexpr (std::is_same_v) { + EXPECT_EQ(result.c1, bb::fq::zero()); + to_be_compared = { result.c0.data[0], result.c0.data[1], result.c0.data[2], result.c0.data[3] }; + } else if constexpr (std::is_same_v) { + EXPECT_EQ(result.c0.c1, bb::fq::zero()); + EXPECT_EQ(result.c1, bb::fq2::zero()); + EXPECT_EQ(result.c2, bb::fq2::zero()); + to_be_compared = { result.c0.c0.data[0], result.c0.c0.data[1], result.c0.c0.data[2], result.c0.c0.data[3] }; + } else { + EXPECT_EQ(result.c0.c0.c1, bb::fq::zero()); + EXPECT_EQ(result.c0.c1, bb::fq2::zero()); + EXPECT_EQ(result.c0.c2, bb::fq2::zero()); + EXPECT_EQ(result.c1, bb::fq6::zero()); + to_be_compared = { + result.c0.c0.c0.data[0], result.c0.c0.c0.data[1], result.c0.c0.c0.data[2], result.c0.c0.c0.data[3] + }; + } + + EXPECT_EQ(to_be_compared, expected); +} + +TYPED_TEST(FieldTest, MontgomeryConsistencyCheck) +{ + using FF = TypeParam; + + FF a = FF::random_element(); + FF b = FF::random_element(); + FF aR; + FF bR; + FF aRR; + FF bRR; + FF bRRR; + FF result_a; + FF result_b; + FF result_c; + FF result_d; + + aR = a.to_montgomery_form(); + aRR = aR.to_montgomery_form(); + bR = b.to_montgomery_form(); + bRR = bR.to_montgomery_form(); + bRRR = bRR.to_montgomery_form(); + result_a = aRR * bRR; // abRRR + result_b = aR * bRRR; // abRRR + result_c = aR * bR; // abR + result_d = a * b; // abR^-1 + + EXPECT_EQ((result_a == result_b), true); + + if constexpr (!IsExtensionField || std::is_same_v) { + result_a.self_from_montgomery_form(); // abRR + result_a.self_from_montgomery_form(); // abR + result_a.self_from_montgomery_form(); // ab + result_c.self_from_montgomery_form(); // ab + result_d.self_to_montgomery_form(); // ab + + EXPECT_EQ((result_a == result_c), true); + EXPECT_EQ((result_a == result_d), true); + } +} + +// ================================ +// Other tests +// ================================ + +TYPED_TEST(FieldTest, Copy) +{ + if constexpr (!IsExtensionField) { + using FF = TypeParam; + + FF result = FF::random_element(); + FF expected; + FF::__copy(result, expected); + + EXPECT_EQ((result == expected), true); + } +} + +TYPED_TEST(FieldTest, SerializeToBuffer) +{ + if constexpr (!IsExtensionField) { + using FF = TypeParam; + + std::array buffer; + FF a{ 0x1234567876543210, 0x2345678987654321, 0x3456789a98765432, 0x006789abcba98765 }; + a = a.to_montgomery_form(); + + FF::serialize_to_buffer(a, &buffer[0]); + + EXPECT_EQ(buffer[31], 0x10); + EXPECT_EQ(buffer[30], 0x32); + EXPECT_EQ(buffer[29], 0x54); + EXPECT_EQ(buffer[28], 0x76); + EXPECT_EQ(buffer[27], 0x78); + EXPECT_EQ(buffer[26], 0x56); + EXPECT_EQ(buffer[25], 0x34); + EXPECT_EQ(buffer[24], 0x12); + + EXPECT_EQ(buffer[23], 0x21); + EXPECT_EQ(buffer[22], 0x43); + EXPECT_EQ(buffer[21], 0x65); + EXPECT_EQ(buffer[20], 0x87); + EXPECT_EQ(buffer[19], 0x89); + EXPECT_EQ(buffer[18], 0x67); + EXPECT_EQ(buffer[17], 0x45); + EXPECT_EQ(buffer[16], 0x23); + + EXPECT_EQ(buffer[15], 0x32); + EXPECT_EQ(buffer[14], 0x54); + EXPECT_EQ(buffer[13], 0x76); + EXPECT_EQ(buffer[12], 0x98); + EXPECT_EQ(buffer[11], 0x9a); + EXPECT_EQ(buffer[10], 0x78); + EXPECT_EQ(buffer[9], 0x56); + EXPECT_EQ(buffer[8], 0x34); + + EXPECT_EQ(buffer[7], 0x65); + EXPECT_EQ(buffer[6], 0x87); + EXPECT_EQ(buffer[5], 0xa9); + EXPECT_EQ(buffer[4], 0xcb); + EXPECT_EQ(buffer[3], 0xab); + EXPECT_EQ(buffer[2], 0x89); + EXPECT_EQ(buffer[1], 0x67); + EXPECT_EQ(buffer[0], 0x00); + } +} + +TYPED_TEST(FieldTest, SerializeFromBuffer) +{ + if constexpr (!IsExtensionField) { + using FF = TypeParam; + + std::array buffer; + FF expected{ 0x1234567876543210, 0x2345678987654321, 0x3456789a98765432, 0x006789abcba98765 }; + + FF::serialize_to_buffer(expected, &buffer[0]); + FF result = FF::serialize_from_buffer(&buffer[0]); + + EXPECT_EQ(result, expected); + } else if constexpr (std::is_same_v) { + std::array buffer; + fq expected_c0 = { 0x1234567876543210, 0x2345678987654321, 0x3456789a98765432, 0x006789abcba98765 }; + fq expected_c1 = { 0x12a4e67f76b43210, 0x23e56f898a65cc21, 0x005678add98e5432, 0x1f6789a2cba98700 }; + fq2 expected{ expected_c0, expected_c1 }; + + fq2::serialize_to_buffer(expected, &buffer[0]); + + fq2 result = fq2::serialize_from_buffer(&buffer[0]); + + EXPECT_EQ(result, expected); + } +} diff --git a/barretenberg/cpp/src/barretenberg/ecc/fields/parameter_helper.py b/barretenberg/cpp/src/barretenberg/ecc/fields/parameter_helper.py index 7713a18d61b6..e960a71889f8 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/fields/parameter_helper.py +++ b/barretenberg/cpp/src/barretenberg/ecc/fields/parameter_helper.py @@ -2,6 +2,8 @@ # A helper script to parse the field parameters from the source code import os +import re +import json parameter_files = ['src/barretenberg/ecc/curves/bn254/fq.hpp', 'src/barretenberg/ecc/curves/bn254/fr.hpp', 'src/barretenberg/ecc/curves/secp256k1/secp256k1.hpp','src/barretenberg/ecc/curves/secp256r1/secp256r1.hpp'] @@ -10,16 +12,14 @@ def get_file_location(): """ Returns the current file's location relative to the execution folder. - + Returns: str: The path to the current file relative to the execution folder. """ return os.path.abspath(__file__) full_paths=[os.path.join(os.path.join(os.path.dirname(get_file_location()),'../../../../'), file) for file in parameter_files] -print(full_paths) -import re def parse_field_params(s): def parse_number(line): """Expects a string without whitespaces""" @@ -29,7 +29,7 @@ def parse_number(line): else: value = int(line) return value - + def recover_single_value(name): """Extract a single value from the source code by finding its name, equals sign, and semicolon""" nonlocal s @@ -38,8 +38,8 @@ def recover_single_value(name): raise ValueError("Couldn't find value with name "+name) eq_position=s[index:].find('=') line_end=s[index:].find(';') - return parse_number(s[index+eq_position+1:index+line_end]) - + return parse_number(s[index+eq_position+1:index+line_end]) + def recover_single_value_if_present(name): """Same as recover_single_value but returns None if the value is not found""" nonlocal s @@ -48,8 +48,8 @@ def recover_single_value_if_present(name): return None eq_position=s[index:].find('=') line_end=s[index:].find(';') - return parse_number(s[index+eq_position+1:index+line_end]) - + return parse_number(s[index+eq_position+1:index+line_end]) + def recover_array(name): """Extract an array of values from the source code by finding its name and contents between braces""" nonlocal s @@ -60,7 +60,7 @@ def recover_array(name): all_values=s[index+start_index+1:index+end_index] result=[parse_number(x) for (i,x) in enumerate(all_values.split(',')) if i& relation_paramete const FF beta = FF::random_element(); const FF gamma = FF::random_element(); const FF beta_sqr = beta * beta; + const FF beta_quartic = beta_sqr * beta_sqr; relation_parameters.gamma = gamma; relation_parameters.beta = beta; relation_parameters.beta_sqr = beta_sqr; relation_parameters.beta_cube = beta_sqr * beta; - relation_parameters.eccvm_set_permutation_delta = - gamma * (gamma + beta_sqr) * (gamma + beta_sqr + beta_sqr) * (gamma + beta_sqr + beta_sqr + beta_sqr); + relation_parameters.beta_quartic = beta_quartic; + auto first_term_tag = beta_quartic; // FIRST_TERM_TAG (= 1) * beta_quartic + relation_parameters.eccvm_set_permutation_delta = (gamma + first_term_tag) * (gamma + beta_sqr + first_term_tag) * + (gamma + beta_sqr + beta_sqr + first_term_tag) * + (gamma + beta_sqr + beta_sqr + beta_sqr + first_term_tag); relation_parameters.eccvm_set_permutation_delta = relation_parameters.eccvm_set_permutation_delta.invert(); const size_t unmasked_witness_size = pk->circuit_size - NUM_DISABLED_ROWS_IN_SUMCHECK; diff --git a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_prover.cpp b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_prover.cpp index e784ad8fb198..065cb9c4b3e6 100644 --- a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_prover.cpp +++ b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_prover.cpp @@ -84,17 +84,22 @@ void ECCVMProver::execute_log_derivative_commitments_round() // TODO(#583)(@zac-williamson): fix Transcript to be able to generate more than 2 challenges per round! oof. auto beta_sqr = beta * beta; + auto beta_quartic = beta_sqr * beta_sqr; relation_parameters.gamma = gamma; relation_parameters.beta = beta; relation_parameters.beta_sqr = beta_sqr; relation_parameters.beta_cube = beta_sqr * beta; + relation_parameters.beta_quartic = beta_quartic; // `eccvm_set_permutation_delta` is used in the set membership gadget in eccvm/ecc_set_relation.hpp, specifically to // constrain (pc, round, wnaf_slice) to match between the MSM table and the Precomputed table. The number of rows we // add per short scalar `mul` is slightly less in the Precomputed table as in the MSM table, so to get the // permutation argument to work out, when `precompute_select == 0`, we must implicitly _remove_ (0, 0, 0) as a tuple - // on the wNAF side. This corresponds to dividing by (γ)·(γ + β²)·(γ + 2β²)·(γ + 3β²). - relation_parameters.eccvm_set_permutation_delta = - gamma * (gamma + beta_sqr) * (gamma + beta_sqr + beta_sqr) * (gamma + beta_sqr + beta_sqr + beta_sqr); + // on the wNAF side. This corresponds to dividing by + // (γ+t·β⁴)·(γ+β²+t·β⁴)·(γ+2β²+t·β⁴)·(γ+3β²+t·β⁴), where t = FIRST_TERM_TAG. + auto first_term_tag = beta_quartic; // FIRST_TERM_TAG (= 1) * beta_quartic + relation_parameters.eccvm_set_permutation_delta = (gamma + first_term_tag) * (gamma + beta_sqr + first_term_tag) * + (gamma + beta_sqr + beta_sqr + first_term_tag) * + (gamma + beta_sqr + beta_sqr + beta_sqr + first_term_tag); relation_parameters.eccvm_set_permutation_delta = relation_parameters.eccvm_set_permutation_delta.invert(); // Compute inverse polynomial for our logarithmic-derivative lookup method compute_logderivative_inverse compute_full_relation_params(ProverPolynomials& polynomia return params; } +/** + * @brief Find the first transcript no-op row: all selectors zero, not first/last row. + */ +size_t find_transcript_noop_row(const ProverPolynomials& polynomials) +{ + const size_t num_rows = polynomials.get_polynomial_size(); + for (size_t i = 2; i < num_rows - 1; i++) { + if (polynomials.transcript_add[i] == FF(0) && polynomials.transcript_mul[i] == FF(0) && + polynomials.transcript_eq[i] == FF(0) && polynomials.transcript_reset_accumulator[i] == FF(0) && + polynomials.lagrange_first[i] == FF(0) && polynomials.lagrange_last[i] == FF(0)) { + return i; + } + } + return 0; +} + } // anonymous namespace class ECCVMRelationCorruptionTests : public ::testing::Test { @@ -330,3 +346,34 @@ TEST_F(ECCVMRelationCorruptionTests, MSMRelationFailsOnShiftedMSMTable) polynomials, full_params, "ECCVMLookupRelation"); EXPECT_TRUE(lookup_failures.empty()) << "ECCVMLookupRelation should still pass (inverse computed post-shift)"; } + +/** + * @brief On a transcript no-op row, setting accumulator_not_empty=1 must be caught by subrelation 22. + * + * @details The `accumulator_infinity_from_noop` term in subrelation 22 forces + * is_accumulator_empty_shift = 1 whenever all selectors are zero. This test corrupts + * the shifted value (i.e. accumulator_not_empty at row+1) to 1 and verifies detection. + */ +TEST_F(ECCVMRelationCorruptionTests, TranscriptNoOpRowRejectsAccumulatorNotEmpty) +{ + auto polynomials = build_valid_eccvm_msm_state(); + RelationParameters params{}; + + auto baseline = + RelationChecker::check>(polynomials, params, "ECCVMTranscriptRelation"); + EXPECT_TRUE(baseline.empty()) << "Baseline transcript relation should pass"; + + size_t noop_row = find_transcript_noop_row(polynomials); + ASSERT_NE(noop_row, 0) << "Should find a transcript no-op row"; + + // The no-op constraint at row `noop_row` constrains is_accumulator_empty_shift, + // which reads from accumulator_not_empty at row `noop_row + 1`. + polynomials.transcript_accumulator_not_empty.at(noop_row + 1) = FF(1); + polynomials.set_shifted(); + + auto failures = + RelationChecker::check>(polynomials, params, "ECCVMTranscriptRelation"); + EXPECT_FALSE(failures.empty()) << "Transcript relation should fail after corrupting accumulator_not_empty on " + "the row following a no-op"; + EXPECT_TRUE(failures.contains(22)) << "Subrelation 22 (accumulator_infinity) should catch the corruption"; +} diff --git a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_trace_checker.cpp b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_trace_checker.cpp index 97d95c92a055..bc78b150ae2c 100644 --- a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_trace_checker.cpp +++ b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_trace_checker.cpp @@ -21,8 +21,11 @@ bool ECCVMTraceChecker::check(Builder& builder, const FF beta = FF::random_element(engine_ptr); const FF beta_sqr = beta.sqr(); const FF beta_cube = beta_sqr * beta; - auto eccvm_set_permutation_delta = - gamma * (gamma + beta_sqr) * (gamma + beta_sqr + beta_sqr) * (gamma + beta_sqr + beta_sqr + beta_sqr); + const FF beta_quartic = beta_sqr * beta_sqr; + auto first_term_tag = beta_quartic; // FIRST_TERM_TAG (= 1) * beta_quartic + auto eccvm_set_permutation_delta = (gamma + first_term_tag) * (gamma + beta_sqr + first_term_tag) * + (gamma + beta_sqr + beta_sqr + first_term_tag) * + (gamma + beta_sqr + beta_sqr + beta_sqr + first_term_tag); eccvm_set_permutation_delta = eccvm_set_permutation_delta.invert(); bb::RelationParameters params{ .eta = 0, @@ -31,6 +34,7 @@ bool ECCVMTraceChecker::check(Builder& builder, .public_input_delta = 0, .beta_sqr = beta_sqr, .beta_cube = beta_cube, + .beta_quartic = beta_quartic, .eccvm_set_permutation_delta = eccvm_set_permutation_delta, }; diff --git a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_verifier.cpp b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_verifier.cpp index 1294cd410902..9bd2765ab2f5 100644 --- a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_verifier.cpp +++ b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_verifier.cpp @@ -6,6 +6,7 @@ #include "./eccvm_verifier.hpp" #include "barretenberg/commitment_schemes/shplonk/shplemini.hpp" +#include "barretenberg/common/bb_bench.hpp" #include "barretenberg/stdlib/eccvm_verifier/eccvm_recursive_flavor.hpp" #include "barretenberg/sumcheck/sumcheck.hpp" #include "barretenberg/transcript/origin_tag.hpp" @@ -19,6 +20,7 @@ namespace bb { template typename ECCVMVerifier_::ReductionResult ECCVMVerifier_::reduce_to_ipa_opening() { + BB_BENCH_NAME("ECCVMVerifier::reduce"); using Curve = typename Flavor::Curve; using Shplemini = ShpleminiVerifier_; using Shplonk = ShplonkVerifier_; @@ -48,12 +50,16 @@ typename ECCVMVerifier_::ReductionResult ECCVMVerifier_::reduce_ auto [beta, gamma] = transcript->template get_challenges(std::array{ "beta", "gamma" }); auto beta_sqr = beta * beta; + auto beta_quartic = beta_sqr * beta_sqr; relation_parameters.gamma = gamma; relation_parameters.beta = beta; - relation_parameters.beta_sqr = beta * beta; + relation_parameters.beta_sqr = beta_sqr; relation_parameters.beta_cube = beta_sqr * beta; - relation_parameters.eccvm_set_permutation_delta = - gamma * (gamma + beta_sqr) * (gamma + beta_sqr + beta_sqr) * (gamma + beta_sqr + beta_sqr + beta_sqr); + relation_parameters.beta_quartic = beta_quartic; + auto first_term_tag = beta_quartic; // FIRST_TERM_TAG (= 1) * beta_quartic + relation_parameters.eccvm_set_permutation_delta = (gamma + first_term_tag) * (gamma + beta_sqr + first_term_tag) * + (gamma + beta_sqr + beta_sqr + first_term_tag) * + (gamma + beta_sqr + beta_sqr + beta_sqr + first_term_tag); relation_parameters.eccvm_set_permutation_delta = relation_parameters.eccvm_set_permutation_delta.invert(); // Get commitment to permutation and lookup grand products diff --git a/barretenberg/cpp/src/barretenberg/goblin/goblin_verifier.cpp b/barretenberg/cpp/src/barretenberg/goblin/goblin_verifier.cpp index 111f928fb6ec..9386174b39b8 100644 --- a/barretenberg/cpp/src/barretenberg/goblin/goblin_verifier.cpp +++ b/barretenberg/cpp/src/barretenberg/goblin/goblin_verifier.cpp @@ -5,6 +5,7 @@ // ===================== #include "goblin_verifier.hpp" +#include "barretenberg/common/bb_bench.hpp" #include "barretenberg/common/log.hpp" namespace bb { @@ -17,6 +18,7 @@ namespace bb { template typename GoblinVerifier_::ReductionResult GoblinVerifier_::reduce_to_pairing_check_and_ipa_opening() { + BB_BENCH_NAME("GoblinVerifier::reduce"); // Step 1: Verify the merge proof MergeVerifier merge_verifier{ merge_settings, transcript }; auto merge_result = merge_verifier.reduce_to_pairing_check(proof.merge_proof, merge_commitments); diff --git a/barretenberg/cpp/src/barretenberg/goblin/merge_verifier.cpp b/barretenberg/cpp/src/barretenberg/goblin/merge_verifier.cpp index 326f276f4293..dcdea114037a 100644 --- a/barretenberg/cpp/src/barretenberg/goblin/merge_verifier.cpp +++ b/barretenberg/cpp/src/barretenberg/goblin/merge_verifier.cpp @@ -6,6 +6,7 @@ #include "merge_verifier.hpp" #include "barretenberg/commitment_schemes/shplonk/shplonk.hpp" +#include "barretenberg/common/bb_bench.hpp" #include "barretenberg/common/log.hpp" #include "barretenberg/stdlib/primitives/curves/bn254.hpp" #include "barretenberg/stdlib/proof/proof.hpp" @@ -114,6 +115,7 @@ template typename MergeVerifier_::ReductionResult MergeVerifier_::reduce_to_pairing_check( const Proof& proof, const InputCommitments& input_commitments) { + BB_BENCH_NAME("MergeVerifier::reduce"); transcript->load_proof(proof); // Receive shift size from prover diff --git a/barretenberg/cpp/src/barretenberg/hypernova/hypernova_decider_prover.cpp b/barretenberg/cpp/src/barretenberg/hypernova/hypernova_decider_prover.cpp index 15a1c340c710..b3df369f23f0 100644 --- a/barretenberg/cpp/src/barretenberg/hypernova/hypernova_decider_prover.cpp +++ b/barretenberg/cpp/src/barretenberg/hypernova/hypernova_decider_prover.cpp @@ -11,17 +11,19 @@ HonkProof HypernovaDeciderProver::construct_proof(Accumulator& accumulator) { vinfo("HypernovaFoldingDecider: prove PCS..."); - size_t actual_size = accumulator.non_shifted_polynomial.virtual_size(); - CommitmentKey ck(actual_size); + size_t dyadic_size = accumulator.dyadic_size; + size_t actual_data_size = + std::max(accumulator.non_shifted_polynomial.end_index(), accumulator.shifted_polynomial.end_index()); + CommitmentKey ck(actual_data_size); // Open the commitments with Shplemini - PolynomialBatcher polynomial_batcher(actual_size); + PolynomialBatcher polynomial_batcher(dyadic_size, actual_data_size); polynomial_batcher.set_unshifted(RefVector(accumulator.non_shifted_polynomial)); polynomial_batcher.set_to_be_shifted_by_one(RefVector(accumulator.shifted_polynomial)); OpeningClaim prover_opening_claim; prover_opening_claim = - ShpleminiProver::prove(actual_size, polynomial_batcher, accumulator.challenge, ck, transcript); + ShpleminiProver::prove(dyadic_size, polynomial_batcher, accumulator.challenge, ck, transcript); vinfo("HypernovaFoldingDecider: executed multivariate-to-univariate reduction"); diff --git a/barretenberg/cpp/src/barretenberg/multilinear_batching/multilinear_batching_prover.cpp b/barretenberg/cpp/src/barretenberg/multilinear_batching/multilinear_batching_prover.cpp index 4d1b16509dd9..29a3396862cf 100644 --- a/barretenberg/cpp/src/barretenberg/multilinear_batching/multilinear_batching_prover.cpp +++ b/barretenberg/cpp/src/barretenberg/multilinear_batching/multilinear_batching_prover.cpp @@ -64,14 +64,25 @@ MultilinearBatchingProverClaim MultilinearBatchingProver::compute_new_claim() auto claim_batching_challenge = transcript->get_challenge("claim_batching_challenge"); // New polynomials - // TODO(https://github.com/AztecProtocol/barretenberg/issues/1604): Optimize new claim computation - bb::Polynomial new_non_shifted_polynomial(key.circuit_size); - new_non_shifted_polynomial += key.polynomials.batched_unshifted_instance; - new_non_shifted_polynomial.add_scaled(key.polynomials.batched_unshifted_accumulator, claim_batching_challenge); - - bb::Polynomial new_shifted_polynomial(bb::Polynomial::shiftable(key.circuit_size)); - new_shifted_polynomial += key.preshifted_instance; - new_shifted_polynomial.add_scaled(key.preshifted_accumulator, claim_batching_challenge); + bb::Polynomial new_non_shifted_polynomial; + if (key.polynomials.batched_unshifted_instance.size() > key.polynomials.batched_unshifted_accumulator.size()) { + new_non_shifted_polynomial = std::move(key.polynomials.batched_unshifted_instance); + new_non_shifted_polynomial.add_scaled(key.polynomials.batched_unshifted_accumulator, claim_batching_challenge); + } else { + new_non_shifted_polynomial = std::move(key.polynomials.batched_unshifted_accumulator); + new_non_shifted_polynomial *= claim_batching_challenge; + new_non_shifted_polynomial += key.polynomials.batched_unshifted_instance; + } + + bb::Polynomial new_shifted_polynomial; + if (key.preshifted_instance.size() > key.preshifted_accumulator.size()) { + new_shifted_polynomial = std::move(key.preshifted_instance); + new_shifted_polynomial.add_scaled(key.preshifted_accumulator, claim_batching_challenge); + } else { + new_shifted_polynomial = std::move(key.preshifted_accumulator); + new_shifted_polynomial *= claim_batching_challenge; + new_shifted_polynomial += key.preshifted_instance; + } // New commitments auto new_non_shifted_commitment = diff --git a/barretenberg/cpp/src/barretenberg/polynomials/backing_memory.hpp b/barretenberg/cpp/src/barretenberg/polynomials/backing_memory.hpp index 893946fcf632..98ec54a242b8 100644 --- a/barretenberg/cpp/src/barretenberg/polynomials/backing_memory.hpp +++ b/barretenberg/cpp/src/barretenberg/polynomials/backing_memory.hpp @@ -129,12 +129,21 @@ template struct BackingMemory { return false; } + if (size > std::numeric_limits::max() / sizeof(Fr)) { + return false; + } + size_t required_bytes = size * sizeof(Fr); - size_t current_usage = current_storage_usage.load(); - // Check if we're under the storage budget - if (current_usage + required_bytes > storage_budget) { - return false; + // Check and update storage usage to enforce budget + size_t current_usage = current_storage_usage.load(); + while (true) { + if (current_usage + required_bytes > storage_budget) { + return false; + } + if (current_storage_usage.compare_exchange_weak(current_usage, current_usage + required_bytes)) { + break; + } } size_t file_size = required_bytes; @@ -152,12 +161,14 @@ template struct BackingMemory { int fd = open(filename.c_str(), O_CREAT | O_RDWR | O_TRUNC, 0644); if (fd < 0) { + current_storage_usage.fetch_sub(required_bytes); return false; } if (ftruncate(fd, static_cast(file_size)) != 0) { close(fd); std::filesystem::remove(filename); + current_storage_usage.fetch_sub(required_bytes); return false; } @@ -165,6 +176,7 @@ template struct BackingMemory { if (addr == MAP_FAILED) { close(fd); std::filesystem::remove(filename); + current_storage_usage.fetch_sub(required_bytes); return false; } @@ -177,8 +189,6 @@ template struct BackingMemory { memory.raw_data = static_cast(addr); memory.file_backed = std::move(file_backed_data); - current_storage_usage.fetch_add(required_bytes); - return true; } #endif diff --git a/barretenberg/cpp/src/barretenberg/polynomials/barycentric.hpp b/barretenberg/cpp/src/barretenberg/polynomials/barycentric.hpp index 4a823acbdee8..46e371b446f5 100644 --- a/barretenberg/cpp/src/barretenberg/polynomials/barycentric.hpp +++ b/barretenberg/cpp/src/barretenberg/polynomials/barycentric.hpp @@ -9,11 +9,6 @@ #include "barretenberg/ecc/fields/field.hpp" #include -/* Future improvements (see https://github.com/AztecProtocol/barretenberg/issues/10): The code works for its intended - * use but could be improved: Precomputing for all possible size pairs is - * probably feasible and might be a better solution than instantiating many instances separately. Then perhaps we could - * infer input type to `extend`. - */ namespace bb { /** diff --git a/barretenberg/cpp/src/barretenberg/polynomials/evaluation_domain.cpp b/barretenberg/cpp/src/barretenberg/polynomials/evaluation_domain.cpp index b00ae907decd..9dcc8d4df4d4 100644 --- a/barretenberg/cpp/src/barretenberg/polynomials/evaluation_domain.cpp +++ b/barretenberg/cpp/src/barretenberg/polynomials/evaluation_domain.cpp @@ -68,8 +68,8 @@ EvaluationDomain::EvaluationDomain(const size_t domain_size, const size_t ta , generator_size(target_generator_size ? target_generator_size : domain_size) , domain(Fr{ size, 0, 0, 0 }.to_montgomery_form()) , domain_inverse(domain.invert()) - , generator(Fr::template coset_generator<0>()) - , generator_inverse(Fr::template coset_generator<0>().invert()) + , generator(Fr::coset_generator()) + , generator_inverse(Fr::coset_generator().invert()) , roots(nullptr) { // Grumpkin does not have many roots of unity and, given these are not used for Honk, we set it to one. diff --git a/barretenberg/cpp/src/barretenberg/polynomials/iterate_over_domain.hpp b/barretenberg/cpp/src/barretenberg/polynomials/iterate_over_domain.hpp deleted file mode 100644 index 9483d1411e0b..000000000000 --- a/barretenberg/cpp/src/barretenberg/polynomials/iterate_over_domain.hpp +++ /dev/null @@ -1,47 +0,0 @@ -// === AUDIT STATUS === -// internal: { status: Planned, auditors: [], commit: } -// external_1: { status: not started, auditors: [], commit: } -// external_2: { status: not started, auditors: [], commit: } -// ===================== - -#pragma once - -#if 0 -#include -#define ITERATE_OVER_DOMAIN_START(domain) \ - { \ - const size_t __num_threads = domain.num_threads; \ - const size_t __thread_size = domain.thread_size; \ - std::vector threads(__num_threads); \ - auto parallel_loop = [&](size_t __start, size_t __end) { \ - for (size_t i = __start; i < __end; ++i) \ - { - -#define ITERATE_OVER_DOMAIN_END \ - } \ - } \ - ; \ - for (size_t j = 0; j < __num_threads; ++j) { \ - const size_t _start = j * __thread_size; \ - const size_t _end = (j + 1) * __thread_size; \ - threads[j] = std::thread(parallel_loop, _start, _end); \ - } \ - for (size_t j = 0; j < __num_threads; ++j) { \ - threads[j].join(); \ - } \ - } -#endif - -// TODO: Evil preprocessor! Can we not just leverage lambdas? -#if 1 -#include -#define ITERATE_OVER_DOMAIN_START(domain) \ - parallel_for(domain.num_threads, [&](size_t j) { \ - const size_t internal_bound_start = j * domain.thread_size; \ - const size_t internal_bound_end = (j + 1) * domain.thread_size; \ - for (size_t i = internal_bound_start; i < internal_bound_end; ++i) { - -#define ITERATE_OVER_DOMAIN_END \ - } \ - }); -#endif \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/polynomials/polynomial_arithmetic.cpp b/barretenberg/cpp/src/barretenberg/polynomials/polynomial_arithmetic.cpp index 36027de7d004..526e7f28cb60 100644 --- a/barretenberg/cpp/src/barretenberg/polynomials/polynomial_arithmetic.cpp +++ b/barretenberg/cpp/src/barretenberg/polynomials/polynomial_arithmetic.cpp @@ -9,7 +9,6 @@ #include "barretenberg/common/mem.hpp" #include "barretenberg/common/thread.hpp" #include "barretenberg/numeric/bitop/get_msb.hpp" -#include "iterate_over_domain.hpp" #include #include #include @@ -158,9 +157,14 @@ template void ifft(Fr* coeffs, Fr* target, const EvaluationDomain& domain) { fft_inner_parallel(coeffs, target, domain, domain.root_inverse, domain.get_inverse_round_roots()); - ITERATE_OVER_DOMAIN_START(domain); - target[i] *= domain.domain_inverse; - ITERATE_OVER_DOMAIN_END; + + parallel_for(domain.num_threads, [&](size_t j) { + const size_t start = j * domain.thread_size; + const size_t end = (j + 1) * domain.thread_size; + for (size_t i = start; i < end; ++i) { + target[i] *= domain.domain_inverse; + } + }); } template Fr evaluate(const Fr* coeffs, const Fr& z, const size_t n) diff --git a/barretenberg/cpp/src/barretenberg/relations/ecc_vm/ecc_set_relation.hpp b/barretenberg/cpp/src/barretenberg/relations/ecc_vm/ecc_set_relation.hpp index 7a2c7ff36820..c0f18ac081b8 100644 --- a/barretenberg/cpp/src/barretenberg/relations/ecc_vm/ecc_set_relation.hpp +++ b/barretenberg/cpp/src/barretenberg/relations/ecc_vm/ecc_set_relation.hpp @@ -18,6 +18,14 @@ template class ECCVMSetRelationImpl { public: using FF = FF_; + // Domain separation tags for the three tuple families in the set relation grand product. + // Each tuple family uses a distinct tag multiplied by beta^4 to prevent cross-family collisions. + // Without these tags, tuples from different families with identical packed values would produce + // identical fingerprints, allowing cross-family substitution in the multiset equality check. + static constexpr uint64_t FIRST_TERM_TAG = 1; // (pc, round, wnaf_slice) + static constexpr uint64_t SECOND_TERM_TAG = 2; // (pc, P.x, P.y, scalar) + static constexpr uint64_t THIRD_TERM_TAG = 3; // (pc, P.x, P.y, msm_size) + static constexpr std::array SUBRELATION_PARTIAL_LENGTHS{ 22, // grand product construction sub-relation 3 // left-shiftable polynomial sub-relation diff --git a/barretenberg/cpp/src/barretenberg/relations/ecc_vm/ecc_set_relation_impl.hpp b/barretenberg/cpp/src/barretenberg/relations/ecc_vm/ecc_set_relation_impl.hpp index d0d387b350f4..980b5230c9f4 100644 --- a/barretenberg/cpp/src/barretenberg/relations/ecc_vm/ecc_set_relation_impl.hpp +++ b/barretenberg/cpp/src/barretenberg/relations/ecc_vm/ecc_set_relation_impl.hpp @@ -80,9 +80,15 @@ Accumulator ECCVMSetRelationImpl::compute_grand_product_numerator(const AllE const auto& beta = params.beta; const auto& beta_sqr = params.beta_sqr; const auto& beta_cube = params.beta_cube; + const auto& beta_quartic = params.beta_quartic; const auto& precompute_pc = View(in.precompute_pc); const auto& precompute_select = View(in.precompute_select); + // Domain separation: each tuple family includes a distinct tag * beta^4 term to prevent cross-family collisions. + const auto first_term_tag = beta_quartic * FIRST_TERM_TAG; + const auto second_term_tag = beta_quartic * SECOND_TERM_TAG; + const auto third_term_tag = beta_quartic * THIRD_TERM_TAG; + /** * @brief First term: tuple of (pc, round, wnaf_slice), computed when slicing scalar multipliers into slices, as * part of ECCVMWnafRelation. @@ -106,7 +112,8 @@ Accumulator ECCVMSetRelationImpl::compute_grand_product_numerator(const AllE wnaf_slice += wnaf_slice; wnaf_slice += s1; - const auto wnaf_slice_input0 = wnaf_slice + gamma + precompute_pc * beta + precompute_round4 * beta_sqr; + const auto wnaf_slice_input0 = + wnaf_slice + gamma + precompute_pc * beta + precompute_round4 * beta_sqr + first_term_tag; numerator *= wnaf_slice_input0; // degree-1 } { @@ -117,7 +124,8 @@ Accumulator ECCVMSetRelationImpl::compute_grand_product_numerator(const AllE wnaf_slice += wnaf_slice; wnaf_slice += s1; - const auto wnaf_slice_input1 = wnaf_slice + gamma + precompute_pc * beta + (precompute_round4 + 1) * beta_sqr; + const auto wnaf_slice_input1 = + wnaf_slice + gamma + precompute_pc * beta + (precompute_round4 + 1) * beta_sqr + first_term_tag; numerator *= wnaf_slice_input1; // degree-2 } { @@ -128,7 +136,8 @@ Accumulator ECCVMSetRelationImpl::compute_grand_product_numerator(const AllE wnaf_slice += wnaf_slice; wnaf_slice += s1; - const auto wnaf_slice_input2 = wnaf_slice + gamma + precompute_pc * beta + (precompute_round4 + 2) * beta_sqr; + const auto wnaf_slice_input2 = + wnaf_slice + gamma + precompute_pc * beta + (precompute_round4 + 2) * beta_sqr + first_term_tag; numerator *= wnaf_slice_input2; // degree-3 } { @@ -138,23 +147,22 @@ Accumulator ECCVMSetRelationImpl::compute_grand_product_numerator(const AllE auto wnaf_slice = s0 + s0; wnaf_slice += wnaf_slice; wnaf_slice += s1; - const auto wnaf_slice_input3 = wnaf_slice + gamma + precompute_pc * beta + (precompute_round4 + 3) * beta_sqr; + const auto wnaf_slice_input3 = + wnaf_slice + gamma + precompute_pc * beta + (precompute_round4 + 3) * beta_sqr + first_term_tag; numerator *= wnaf_slice_input3; // degree-4 } { // skew product if relevant const auto& skew = View(in.precompute_skew); const auto& precompute_point_transition = View(in.precompute_point_transition); - const auto skew_input = - precompute_point_transition * (skew + gamma + precompute_pc * beta + (precompute_round4 + 4) * beta_sqr) + - (-precompute_point_transition + 1); + const auto skew_input = precompute_point_transition * (skew + gamma + precompute_pc * beta + + (precompute_round4 + 4) * beta_sqr + first_term_tag) + + (-precompute_point_transition + 1); numerator *= skew_input; // degree-5 } { // in `EccvmProver` and `ECCVMVerifier`, we see that `eccvm_set_permutation_delta` is initially computed as - // (γ)·(γ + β²)·(γ + 2β²)·(γ + 3β²) and _then_ inverted. Therefore, `eccvm_set_permutation_delta` == 1 / (γ)·(γ - // + β²)·(γ + 2β²)·(γ - // + 3β²) + // (γ+t·β⁴)·(γ+β²+t·β⁴)·(γ+2β²+t·β⁴)·(γ+3β²+t·β⁴) (where t = FIRST_TERM_TAG) and _then_ inverted. const auto& eccvm_set_permutation_delta = params.eccvm_set_permutation_delta; // if `precompute_select == 1`, don't change the numerator. if it is 0, then to get the grand product argument // to work (as we have zero-padded the rows of the MSM table), we must multiply by the inverse of the @@ -237,7 +245,7 @@ Accumulator ECCVMSetRelationImpl::compute_grand_product_numerator(const AllE auto precompute_point_transition = View(in.precompute_point_transition); auto point_table_init_read = - (precompute_pc + table_x * beta + table_y * beta_sqr + scalar_sum_full * beta_cube); + (precompute_pc + table_x * beta + table_y * beta_sqr + scalar_sum_full * beta_cube + second_term_tag); point_table_init_read = precompute_point_transition * (point_table_init_read + gamma) + (-precompute_point_transition + 1); @@ -282,7 +290,8 @@ Accumulator ECCVMSetRelationImpl::compute_grand_product_numerator(const AllE // at row i + 1 we have updated `pc` to be `(pc at start of msm) + msm_count` // at row i + 1 q_msm_transtiion = 1 - auto msm_result_write = msm_pc_shift + msm_x_shift * beta + msm_y_shift * beta_sqr + msm_size * beta_cube; + auto msm_result_write = + msm_pc_shift + msm_x_shift * beta + msm_y_shift * beta_sqr + msm_size * beta_cube + third_term_tag; // msm_result_write = degree 2 msm_result_write = msm_transition_shift * (msm_result_write + gamma) + (-msm_transition_shift + 1); @@ -303,10 +312,16 @@ Accumulator ECCVMSetRelationImpl::compute_grand_product_denominator(const Al const auto& beta = params.beta; const auto& beta_sqr = params.beta_sqr; const auto& beta_cube = params.beta_cube; + const auto& beta_quartic = params.beta_quartic; const auto& msm_pc = View(in.msm_pc); const auto& msm_count = View(in.msm_count); const auto& msm_round = View(in.msm_round); + // Domain separation: must match the tags used in the numerator. + const auto first_term_tag = beta_quartic * FIRST_TERM_TAG; + const auto second_term_tag = beta_quartic * SECOND_TERM_TAG; + const auto third_term_tag = beta_quartic * THIRD_TERM_TAG; + /** * @brief First term: tuple of (pc, round, wnaf_slice), used to determine which points we extract from lookup tables * when evaluaing MSMs in ECCVMMsmRelation. @@ -318,7 +333,8 @@ Accumulator ECCVMSetRelationImpl::compute_grand_product_denominator(const Al const auto& msm_slice1 = View(in.msm_slice1); auto wnaf_slice_output1 = - add1 * (msm_slice1 + gamma + (msm_pc - msm_count) * beta + msm_round * beta_sqr) + (-add1 + 1); + add1 * (msm_slice1 + gamma + (msm_pc - msm_count) * beta + msm_round * beta_sqr + first_term_tag) + + (-add1 + 1); denominator *= wnaf_slice_output1; // degree-2 } { @@ -326,7 +342,8 @@ Accumulator ECCVMSetRelationImpl::compute_grand_product_denominator(const Al const auto& msm_slice2 = View(in.msm_slice2); auto wnaf_slice_output2 = - add2 * (msm_slice2 + gamma + (msm_pc - msm_count - 1) * beta + msm_round * beta_sqr) + (-add2 + 1); + add2 * (msm_slice2 + gamma + (msm_pc - msm_count - 1) * beta + msm_round * beta_sqr + first_term_tag) + + (-add2 + 1); denominator *= wnaf_slice_output2; // degree-4 } { @@ -334,14 +351,16 @@ Accumulator ECCVMSetRelationImpl::compute_grand_product_denominator(const Al const auto& msm_slice3 = View(in.msm_slice3); auto wnaf_slice_output3 = - add3 * (msm_slice3 + gamma + (msm_pc - msm_count - 2) * beta + msm_round * beta_sqr) + (-add3 + 1); + add3 * (msm_slice3 + gamma + (msm_pc - msm_count - 2) * beta + msm_round * beta_sqr + first_term_tag) + + (-add3 + 1); denominator *= wnaf_slice_output3; // degree-6 } { const auto& add4 = View(in.msm_add4); const auto& msm_slice4 = View(in.msm_slice4); auto wnaf_slice_output4 = - add4 * (msm_slice4 + gamma + (msm_pc - msm_count - 3) * beta + msm_round * beta_sqr) + (-add4 + 1); + add4 * (msm_slice4 + gamma + (msm_pc - msm_count - 3) * beta + msm_round * beta_sqr + first_term_tag) + + (-add4 + 1); denominator *= wnaf_slice_output4; // degree-8 } @@ -371,10 +390,10 @@ Accumulator ECCVMSetRelationImpl::compute_grand_product_denominator(const Al const auto& lookup_second = (-z2_zero + 1); FF cube_root_unity = FF(bb::fq::cube_root_of_unity()); - auto transcript_input1 = - transcript_pc + transcript_Px * beta + transcript_Py * beta_sqr + z1 * beta_cube; // degree = 1 - auto transcript_input2 = (transcript_pc - 1) + transcript_Px * cube_root_unity * beta - - transcript_Py * beta_sqr + z2 * beta_cube; // degree = 2 + auto transcript_input1 = transcript_pc + transcript_Px * beta + transcript_Py * beta_sqr + z1 * beta_cube + + second_term_tag; // degree = 1 + auto transcript_input2 = (transcript_pc - lookup_first) + transcript_Px * cube_root_unity * beta - + transcript_Py * beta_sqr + z2 * beta_cube + second_term_tag; // degree = 2 // The following diagram expresses a fingerprint of part of the tuple. It does not include `transcript_pc` and // has not weighted the X and Y with beta and beta_sqr respectively. The point is nonetheless to show exactly @@ -432,8 +451,8 @@ Accumulator ECCVMSetRelationImpl::compute_grand_product_denominator(const Al auto full_msm_count = transcript_msm_count + transcript_mul * ((-z1_zero + 1) + (-z2_zero + 1)) * (-base_infinity + 1); // msm_result_read = degree 2 - auto msm_result_read = - transcript_pc_shift + transcript_msm_x * beta + transcript_msm_y * beta_sqr + full_msm_count * beta_cube; + auto msm_result_read = transcript_pc_shift + transcript_msm_x * beta + transcript_msm_y * beta_sqr + + full_msm_count * beta_cube + third_term_tag; msm_result_read = transcript_msm_transition * (msm_result_read + gamma) + (-transcript_msm_transition + 1); denominator *= msm_result_read; // degree-20 } diff --git a/barretenberg/cpp/src/barretenberg/relations/ecc_vm/ecc_transcript_relation_impl.hpp b/barretenberg/cpp/src/barretenberg/relations/ecc_vm/ecc_transcript_relation_impl.hpp index a29e55ed6b14..626397c46f96 100644 --- a/barretenberg/cpp/src/barretenberg/relations/ecc_vm/ecc_transcript_relation_impl.hpp +++ b/barretenberg/cpp/src/barretenberg/relations/ecc_vm/ecc_transcript_relation_impl.hpp @@ -479,11 +479,20 @@ void ECCVMTranscriptRelationImpl::accumulate(ContainerOverSubrelations& accu } /** - * @brief Validate `is_accumulator_empty` is updated correctly - * An add operation can produce a point at infinity - * Resetting the accumulator produces a point at infinity - * If we are not adding, performing an msm or resetting the accumulator (or doing a no-op), - * is_accumulator_empty should not update + * @brief Validate `is_accumulator_empty` is updated correctly. + * + * The relation is a sum of four mutually exclusive terms, each constraining `is_accumulator_empty_shift` + * for a specific case: + * (A) accumulator_infinity_preserve: active when propagate_transcript_accumulator != 0 + * (i.e. q_mul without msm_transition, or q_eq without q_reset). Preserves the emptiness flag. + * (B) accumulator_infinity_q_reset: active when q_reset_accumulator = 1. Forces empty. + * (B) accumulator_infinity_from_add: active when any_add_is_active != 0 (q_add or msm_transition). + * Sets emptiness from the add/msm result. + * (C) accumulator_infinity_from_noop: active when opcode_is_zero != 0 (all selectors off). Forces empty. + * + * These are mutually exclusive because opcode_is_zero requires all selectors = 0 (which zeros out A and B), + * while A and B each require at least one selector to be non-zero (which zeros out C). + * Within A and B, exclusivity follows from the opcode exclusion constraint (subrelation 8). */ auto accumulator_infinity_preserve_flag = propagate_transcript_accumulator; // degree 1 auto accumulator_infinity_preserve = accumulator_infinity_preserve_flag * @@ -492,10 +501,19 @@ void ECCVMTranscriptRelationImpl::accumulate(ContainerOverSubrelations& accu auto accumulator_infinity_q_reset = q_reset_accumulator * (-is_accumulator_empty_shift + 1); // degree 2 auto accumulator_infinity_from_add = any_add_is_active * (result_is_infinity - is_accumulator_empty_shift); // degree 3 + // When opcode_is_zero (no-op row), the accumulator output is forced to (0,0) by subrelations 15 and 16. + // We must also force is_accumulator_empty_shift = 1 so that the emptiness flag is consistent + // with the (0,0) accumulator coordinates. Without this, a malicious prover could set + // accumulator_not_empty = 1 on the next row while the accumulator is (0,0), creating an + // inconsistency that bypasses on-curve checks (which are only performed on input coordinates). + auto opcode_is_zero = + (is_not_first_row) * (-q_add + 1) * (-q_mul + 1) * (-q_reset_accumulator + 1) * (-q_eq + 1); // degree 5 + auto accumulator_infinity_from_noop = opcode_is_zero * (-is_accumulator_empty_shift + 1); // degree 6 auto accumulator_infinity_relation = accumulator_infinity_preserve + - (accumulator_infinity_q_reset + accumulator_infinity_from_add) * is_not_first_row; // degree 4 - std::get<22>(accumulator) += accumulator_infinity_relation * scaling_factor; // degree 4 + (accumulator_infinity_q_reset + accumulator_infinity_from_add) * is_not_first_row + + accumulator_infinity_from_noop; // degree 6 + std::get<22>(accumulator) += accumulator_infinity_relation * scaling_factor; // degree 6 /** * @brief Validate `transcript_add_x_equal` is well-formed diff --git a/barretenberg/cpp/src/barretenberg/relations/relation_parameters.hpp b/barretenberg/cpp/src/barretenberg/relations/relation_parameters.hpp index c68f7e4889f9..da086e3d0fb8 100644 --- a/barretenberg/cpp/src/barretenberg/relations/relation_parameters.hpp +++ b/barretenberg/cpp/src/barretenberg/relations/relation_parameters.hpp @@ -30,6 +30,7 @@ template struct RelationParameters { T public_input_delta{ 0 }; // Permutation T beta_sqr{ 0 }; T beta_cube{ 0 }; + T beta_quartic{ 0 }; // Compute eta powers from a single eta challenge void compute_eta_powers(const T& eta_challenge) @@ -49,7 +50,8 @@ template struct RelationParameters { // constrain (pc, round, wnaf_slice) to match between the MSM table and the Precomputed table. The number of rows we // add per short scalar `mul` is slightly less in the Precomputed table as in the MSM table, so to get the // permutation argument to work out, when `precompute_select == 0`, we must implicitly _remove_ (0, 0, 0) as a tuple - // on the wNAF side. This corresponds to dividing by (γ)·(γ + β²)·(γ + 2β²)·(γ + 3β²). + // on the wNAF side. This corresponds to dividing by (γ+t·β⁴)·(γ+β²+t·β⁴)·(γ+2β²+t·β⁴)·(γ+3β²+t·β⁴), where + // t = FIRST_TERM_TAG (the domain separation tag for WNAF slice tuples). // // We can remove this by modifying the relation, but this would increase the complexity. T eccvm_set_permutation_delta = T(0); @@ -63,7 +65,7 @@ template struct RelationParameters { { T(0), T(0), T(0), T(0), T(0) }, { T(0), T(0), T(0), T(0), T(0) }, { T(0), T(0), T(0), T(0), T(0) } } }; - + // only used for testing static RelationParameters get_random() { RelationParameters result; @@ -71,9 +73,11 @@ template struct RelationParameters { result.compute_beta_powers(T::random_element()); // beta, beta_sqr = beta², beta_cube = beta³ result.gamma = T::random_element(); result.public_input_delta = T::random_element(); - result.eccvm_set_permutation_delta = result.gamma * (result.gamma + result.beta_sqr) * - (result.gamma + result.beta_sqr + result.beta_sqr) * - (result.gamma + result.beta_sqr + result.beta_sqr + result.beta_sqr); + auto first_term_tag = result.beta_quartic; // FIRST_TERM_TAG (= 1) * beta_quartic + result.eccvm_set_permutation_delta = + (result.gamma + first_term_tag) * (result.gamma + result.beta_sqr + first_term_tag) * + (result.gamma + result.beta_sqr + result.beta_sqr + first_term_tag) * + (result.gamma + result.beta_sqr + result.beta_sqr + result.beta_sqr + first_term_tag); result.accumulated_result = { T::random_element(), T::random_element(), T::random_element(), T::random_element() }; diff --git a/barretenberg/cpp/src/barretenberg/srs/factories/bn254_g1_chunk_hashes.hpp b/barretenberg/cpp/src/barretenberg/srs/factories/bn254_g1_chunk_hashes.hpp new file mode 100644 index 000000000000..43b7d8805a31 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/srs/factories/bn254_g1_chunk_hashes.hpp @@ -0,0 +1,283 @@ +// AUTO-GENERATED by generate_srs_chunk_hashes.py — do not edit manually +#pragma once +#include +#include +#include + +namespace bb::srs { + +// CRS file: bn254_g1.dat (2,147,483,712 bytes = 33,554,433 G1 points) +// Chunk size: 8,388,608 bytes (131,072 points per 8MB chunk) +// Total chunks: 257 (256 full + 1 partial of 64 bytes) +// Regenerate with: barretenberg/scripts/generate_srs_chunk_hashes.py + +static constexpr size_t SRS_CHUNK_SIZE_POINTS = 131072; // 2^17 +static constexpr size_t SRS_CHUNK_SIZE_BYTES = SRS_CHUNK_SIZE_POINTS * 64; // 8MB +static constexpr size_t SRS_TOTAL_POINTS = 33554433; // 2^25 + 1 +static constexpr size_t SRS_NUM_CHUNKS = 257; +static constexpr size_t SRS_NUM_FULL_CHUNKS = 256; + +// clang-format off +// SHA-256 hashes of each 8MB chunk of g1.dat (last chunk is 64 bytes) +static constexpr std::array, 257> BN254_G1_CHUNK_HASHES = {{ + { 0x37, 0x18, 0xff, 0xbe, 0xd5, 0x83, 0xd8, 0xa2, 0x79, 0xcc, 0xb2, 0x54, 0x51, 0x96, 0xd5, 0x61, 0x8e, 0x3f, 0xd9, 0x75, 0x12, 0xb6, 0x5a, 0xa0, 0x51, 0xd2, 0xeb, 0xd0, 0xf5, 0x7f, 0xb9, 0x99 }, + { 0x76, 0x64, 0x32, 0xec, 0xe7, 0xcc, 0x4e, 0x80, 0x4b, 0x4e, 0x09, 0xdc, 0x57, 0xd3, 0xc1, 0x04, 0x82, 0x39, 0xd2, 0xe8, 0xa4, 0x16, 0xeb, 0x16, 0x91, 0x9c, 0x80, 0xa9, 0x18, 0xa3, 0xb5, 0xfd }, + { 0x94, 0x41, 0x49, 0x1e, 0xac, 0xea, 0xdc, 0x6a, 0xa2, 0x63, 0x5d, 0x6f, 0xf1, 0x6f, 0x73, 0x97, 0x76, 0x80, 0x5e, 0xb1, 0xa4, 0xce, 0x77, 0x0d, 0xca, 0xd0, 0x82, 0xec, 0x93, 0x33, 0xbf, 0x9b }, + { 0xc4, 0x2a, 0xef, 0xb8, 0xf7, 0x2c, 0xf5, 0xd7, 0xc4, 0xe4, 0x90, 0x0a, 0x14, 0x10, 0x67, 0x11, 0xed, 0xe3, 0x65, 0x76, 0x15, 0x94, 0x9d, 0x17, 0xd5, 0xe7, 0x17, 0x92, 0xf6, 0x21, 0xe4, 0xdb }, + { 0x33, 0x08, 0x56, 0x42, 0x28, 0x38, 0x90, 0xeb, 0xe1, 0xd4, 0x2d, 0xf4, 0xf2, 0x85, 0x73, 0x82, 0x22, 0xd6, 0x3d, 0xf3, 0x19, 0x08, 0xaf, 0xe9, 0x3a, 0x13, 0xcf, 0x9d, 0xff, 0x62, 0x70, 0xa9 }, + { 0x95, 0xf2, 0x93, 0xf2, 0x30, 0x92, 0x08, 0x1a, 0xfd, 0x73, 0x1c, 0xf4, 0x90, 0x4e, 0x0f, 0x0a, 0x70, 0x5e, 0x1f, 0x5d, 0x53, 0x24, 0x9e, 0xee, 0x67, 0x40, 0x1c, 0x60, 0x70, 0xef, 0x32, 0x7d }, + { 0x04, 0xd2, 0x3e, 0xb3, 0xe4, 0x45, 0x6f, 0x71, 0x41, 0x3c, 0xcc, 0x51, 0x86, 0x7c, 0x0a, 0x57, 0x15, 0xfc, 0xe4, 0x78, 0x26, 0xf5, 0xf5, 0xdd, 0xa7, 0x2a, 0xbb, 0x4f, 0xfd, 0xa0, 0x72, 0x79 }, + { 0xf3, 0x6a, 0x99, 0xbe, 0xac, 0x7d, 0xec, 0x7b, 0x46, 0x07, 0x37, 0x6d, 0x8e, 0xdd, 0xbc, 0x99, 0x15, 0x7a, 0x4a, 0x59, 0xf7, 0x5e, 0xac, 0x98, 0x87, 0x0f, 0x07, 0xac, 0x54, 0xdb, 0xf2, 0x12 }, + { 0x7b, 0xfd, 0xa8, 0x24, 0x41, 0xab, 0x5e, 0x00, 0xc2, 0xcb, 0x06, 0x13, 0x8e, 0xf4, 0x93, 0xc8, 0x9d, 0x94, 0xe9, 0x71, 0x19, 0xa3, 0xb1, 0x46, 0x33, 0x9c, 0xd3, 0x35, 0x04, 0x9a, 0xeb, 0x63 }, + { 0x45, 0xf4, 0xa4, 0x61, 0x23, 0xc4, 0x34, 0xb7, 0x80, 0x0f, 0x6b, 0x36, 0xae, 0xf4, 0x3e, 0x28, 0xe6, 0x36, 0xdf, 0x87, 0x15, 0x86, 0x93, 0xf4, 0xbb, 0x9d, 0x88, 0xb5, 0xc9, 0x36, 0x4c, 0xd7 }, + { 0x16, 0x41, 0xc3, 0x00, 0x5f, 0x43, 0x4d, 0x8e, 0xc3, 0xf0, 0x4a, 0x1a, 0x5c, 0x94, 0xf6, 0x60, 0x96, 0x62, 0xf1, 0x03, 0x7c, 0x49, 0x04, 0x1d, 0x47, 0x7c, 0x6e, 0x17, 0xc7, 0x3b, 0x17, 0x92 }, + { 0xbe, 0x89, 0xb5, 0x7a, 0xd5, 0x08, 0x93, 0x19, 0x77, 0xaa, 0x22, 0x50, 0x50, 0x95, 0x0d, 0x67, 0x67, 0xeb, 0x2f, 0x9f, 0x96, 0x2a, 0x19, 0x6a, 0x9a, 0x11, 0x45, 0x2c, 0x73, 0xd6, 0xec, 0x1b }, + { 0xc4, 0x86, 0x38, 0xfc, 0x95, 0x37, 0x01, 0x56, 0x09, 0x2d, 0x5c, 0x4d, 0x0b, 0x99, 0x8c, 0x16, 0x1c, 0x3e, 0x3d, 0x48, 0x2a, 0x16, 0xc1, 0xd9, 0xc8, 0xd2, 0x87, 0xa9, 0x4e, 0xb0, 0x49, 0x10 }, + { 0x9c, 0x99, 0xa4, 0xf4, 0xbb, 0xa4, 0xba, 0x9f, 0x52, 0x41, 0x17, 0x78, 0x0d, 0x3f, 0x04, 0x3d, 0xbf, 0xeb, 0xee, 0x92, 0x4d, 0xc1, 0x52, 0x68, 0xc1, 0xf4, 0x9c, 0x6e, 0x40, 0xf1, 0x16, 0x35 }, + { 0x64, 0x2f, 0xc5, 0x9b, 0xd6, 0x11, 0xe7, 0x7c, 0x2b, 0x97, 0xb5, 0x87, 0x85, 0x40, 0x43, 0x4f, 0xfc, 0x6b, 0x75, 0xd9, 0x51, 0x25, 0x59, 0x34, 0x66, 0xbc, 0xe6, 0x77, 0x08, 0x33, 0x6c, 0x74 }, + { 0x46, 0x23, 0x19, 0x98, 0x71, 0xba, 0x5c, 0xc3, 0x1d, 0x37, 0xce, 0xe1, 0x01, 0xc1, 0x2e, 0xb3, 0x96, 0xf6, 0xe4, 0x53, 0x7c, 0x89, 0x96, 0xa0, 0xbe, 0x0c, 0x43, 0xe3, 0x65, 0xfc, 0xaa, 0x7c }, + { 0xf3, 0x3c, 0x73, 0xc4, 0x86, 0x7c, 0xe1, 0x63, 0x1c, 0x89, 0xeb, 0x83, 0x06, 0x79, 0x8f, 0x7b, 0xec, 0x27, 0xd1, 0x8b, 0x1b, 0x23, 0x1f, 0x37, 0x01, 0xbf, 0xc0, 0xa7, 0x2b, 0x44, 0x95, 0x5d }, + { 0x5a, 0x93, 0x51, 0xf3, 0x82, 0xaf, 0x1e, 0x80, 0x42, 0xb2, 0xe2, 0xf3, 0x1d, 0x80, 0xe6, 0xe4, 0x4c, 0xe0, 0xaf, 0xdd, 0x71, 0x86, 0xc7, 0x6a, 0x28, 0x38, 0x4f, 0x85, 0xf9, 0x68, 0x68, 0x9b }, + { 0x2a, 0x39, 0x1b, 0xe1, 0x84, 0x8a, 0xe2, 0x80, 0x54, 0xdc, 0x67, 0x78, 0xe2, 0x23, 0x44, 0x9f, 0x38, 0xdf, 0x40, 0xf2, 0x1a, 0x69, 0x80, 0x51, 0x3c, 0xba, 0x00, 0xde, 0xa8, 0x55, 0x25, 0x05 }, + { 0xca, 0x1b, 0xee, 0x70, 0x4c, 0x89, 0xb2, 0xcf, 0xba, 0xac, 0xe7, 0xfc, 0xf0, 0xed, 0xec, 0x60, 0x13, 0x95, 0xa1, 0xd2, 0x0d, 0xe2, 0x38, 0x4e, 0xd1, 0x62, 0x3d, 0x4e, 0x23, 0xce, 0xda, 0xd9 }, + { 0x6a, 0x22, 0x81, 0x77, 0xcd, 0x17, 0x2a, 0x14, 0x7d, 0x10, 0x50, 0xb6, 0xb4, 0xb9, 0x5d, 0xcb, 0xc2, 0xbc, 0xfc, 0xf7, 0x94, 0x6a, 0x1e, 0xbf, 0x26, 0xe8, 0x11, 0xfb, 0xe1, 0x68, 0x6a, 0xc7 }, + { 0x6c, 0xa0, 0xd3, 0x55, 0xa6, 0xa7, 0x7a, 0x41, 0xb8, 0x52, 0xd9, 0x37, 0xc1, 0xe5, 0x55, 0xae, 0xf4, 0x11, 0xc8, 0x9f, 0xe9, 0x72, 0x9e, 0x2b, 0x97, 0x14, 0xd6, 0x4b, 0x02, 0xda, 0x5c, 0x6d }, + { 0x32, 0x38, 0x0e, 0xc8, 0x68, 0x00, 0x68, 0x9a, 0xeb, 0x8b, 0xf8, 0x25, 0x89, 0xcf, 0x7e, 0xe1, 0x62, 0xb7, 0x63, 0x4e, 0x02, 0x9b, 0xe3, 0xac, 0x0c, 0x42, 0xbc, 0x08, 0x8b, 0xa9, 0x5c, 0x74 }, + { 0xa9, 0x39, 0xe2, 0x3a, 0xf2, 0x37, 0x55, 0xcb, 0x46, 0x28, 0xce, 0xba, 0x8e, 0x54, 0x6e, 0x49, 0xdf, 0xd6, 0x4d, 0xa9, 0x29, 0x13, 0x73, 0x18, 0x17, 0x51, 0xad, 0xa5, 0x30, 0xb3, 0x14, 0x6a }, + { 0xa4, 0xcb, 0x65, 0x63, 0x5b, 0x86, 0x84, 0x63, 0x30, 0x23, 0xed, 0x99, 0xbc, 0x24, 0x69, 0xa0, 0xb7, 0xf3, 0xda, 0x9c, 0xb2, 0xf5, 0x26, 0xfc, 0x48, 0xf4, 0x7b, 0x55, 0x52, 0x26, 0x87, 0xe5 }, + { 0xf7, 0x6b, 0xe9, 0xb9, 0x1a, 0x4d, 0xfd, 0x96, 0xc4, 0x1f, 0x1f, 0x37, 0x38, 0x4e, 0x46, 0x02, 0xbe, 0x26, 0x8b, 0xde, 0x63, 0x0a, 0x38, 0x1a, 0xd5, 0xe4, 0xc0, 0x02, 0xd1, 0x9f, 0x53, 0x33 }, + { 0x0d, 0x4a, 0x07, 0x56, 0x39, 0x6b, 0x3c, 0x5b, 0xe3, 0xfc, 0x3c, 0xf7, 0xc7, 0xa3, 0x8d, 0xfc, 0x3e, 0xd8, 0xc3, 0x68, 0x81, 0xcc, 0x50, 0x06, 0x8b, 0x15, 0x99, 0xe0, 0xe3, 0x40, 0x04, 0x4f }, + { 0x6f, 0xba, 0xdf, 0x0b, 0xa5, 0xb3, 0x55, 0xbf, 0x3f, 0x29, 0x67, 0xf2, 0x30, 0x52, 0xd8, 0x5e, 0x5a, 0x4d, 0xba, 0x05, 0x17, 0xb3, 0x34, 0x94, 0x98, 0x8d, 0x4f, 0xcb, 0xbd, 0x6b, 0xb7, 0xb6 }, + { 0x56, 0xba, 0xbf, 0x37, 0x0b, 0x5b, 0xd0, 0x6f, 0x66, 0x78, 0x40, 0xe1, 0x16, 0x27, 0xba, 0x31, 0xa1, 0xa2, 0xc3, 0xb2, 0x03, 0x33, 0x94, 0xd0, 0xcb, 0x33, 0x77, 0x54, 0xb3, 0xc8, 0x51, 0xc5 }, + { 0x4f, 0x61, 0xf8, 0xa5, 0x57, 0x13, 0x98, 0x05, 0x15, 0x5e, 0xd9, 0xc9, 0x15, 0xa9, 0x8a, 0x86, 0x1d, 0xad, 0xe7, 0x73, 0x55, 0xd1, 0x95, 0xbd, 0x7f, 0x9b, 0x9c, 0xa2, 0x00, 0xd9, 0xaf, 0x2e }, + { 0xb2, 0xbc, 0x21, 0x4c, 0x39, 0xcb, 0x44, 0x4e, 0x57, 0x48, 0xc8, 0xe5, 0xc4, 0x7a, 0xd9, 0xe6, 0x6a, 0x01, 0xac, 0xfc, 0x69, 0x1c, 0x45, 0x71, 0xa3, 0x6c, 0xf7, 0x43, 0x11, 0x14, 0x49, 0x18 }, + { 0xff, 0x82, 0xf0, 0xa6, 0x0f, 0x47, 0x10, 0x1b, 0x2a, 0x0c, 0xc6, 0x56, 0x53, 0xfa, 0x2b, 0xce, 0xdd, 0xd7, 0x58, 0xba, 0x75, 0xc4, 0xf4, 0xbd, 0xd7, 0xde, 0xcb, 0x10, 0xe7, 0x05, 0xbf, 0x69 }, + { 0xea, 0xaa, 0xe0, 0x47, 0xb3, 0xc1, 0xc4, 0xb8, 0x4d, 0xec, 0xc3, 0x19, 0xe6, 0x4b, 0x0a, 0x9f, 0x1d, 0x78, 0xbf, 0x96, 0x79, 0x83, 0x16, 0xeb, 0x40, 0xc8, 0xb3, 0x84, 0x40, 0x02, 0x8b, 0x4e }, + { 0xdf, 0xde, 0x1a, 0xde, 0x2e, 0xda, 0xdd, 0xc9, 0xd9, 0x62, 0x97, 0x88, 0x11, 0x83, 0xc2, 0x56, 0xe0, 0x52, 0xf5, 0x69, 0xcc, 0xa3, 0x28, 0xd0, 0x12, 0x29, 0xb8, 0x17, 0x17, 0xbf, 0xf4, 0x1e }, + { 0x2b, 0xcf, 0x6f, 0x27, 0x65, 0x4e, 0x2f, 0x09, 0xcb, 0xd4, 0x7c, 0xf6, 0xf1, 0x24, 0xcb, 0x3b, 0x72, 0x41, 0x4e, 0xb4, 0x5a, 0xd0, 0x03, 0x32, 0x63, 0x2e, 0x3e, 0xbc, 0x80, 0x6a, 0x6c, 0x6f }, + { 0xb9, 0x57, 0x02, 0xea, 0x42, 0x68, 0xe2, 0xf2, 0x3a, 0xb1, 0x62, 0x53, 0xcf, 0x19, 0xf7, 0xbe, 0xdf, 0xd4, 0xa6, 0x4f, 0x4f, 0x92, 0x87, 0x1b, 0xbb, 0xb7, 0xf4, 0x7c, 0x61, 0x5e, 0x9d, 0x8b }, + { 0xe6, 0x48, 0x3a, 0x34, 0x2f, 0x00, 0xed, 0xa4, 0x4f, 0x1d, 0x6f, 0xcb, 0xdb, 0x02, 0x8e, 0x92, 0x51, 0x27, 0xa1, 0x9b, 0x87, 0x67, 0x2d, 0x44, 0x75, 0x4a, 0x62, 0x90, 0x96, 0x93, 0xe5, 0x08 }, + { 0x74, 0x0b, 0x38, 0x19, 0xb3, 0x63, 0xdc, 0xcf, 0xb5, 0x91, 0x06, 0x86, 0x18, 0xef, 0x32, 0x57, 0x4e, 0xc9, 0xbd, 0x6e, 0x00, 0xe8, 0xde, 0xdb, 0x36, 0xf6, 0xbe, 0x1a, 0xc9, 0xe6, 0x9c, 0x05 }, + { 0x20, 0x79, 0x64, 0xfc, 0x7b, 0xc1, 0x6c, 0x78, 0x76, 0x4b, 0xec, 0xca, 0x58, 0x57, 0x64, 0x3b, 0xe4, 0xde, 0x2d, 0xfb, 0xa1, 0x55, 0x98, 0x67, 0x79, 0x67, 0x30, 0x0e, 0xbe, 0xd8, 0x82, 0xfe }, + { 0xc9, 0x91, 0xde, 0x9d, 0xa1, 0xea, 0x37, 0x7b, 0x9d, 0x01, 0xb7, 0x56, 0xbb, 0x97, 0x64, 0xa0, 0x56, 0x50, 0x0b, 0x36, 0x4d, 0x13, 0x00, 0xc1, 0x42, 0x76, 0x55, 0x76, 0xae, 0x31, 0x15, 0x74 }, + { 0xa5, 0xe4, 0x0e, 0x2a, 0x9b, 0x93, 0x64, 0x33, 0x34, 0x28, 0x38, 0xd6, 0x4f, 0x36, 0xc3, 0xf6, 0x6e, 0xc0, 0xd1, 0x06, 0x03, 0x0f, 0x90, 0xf0, 0x90, 0xe9, 0x00, 0xae, 0x83, 0xe7, 0x8b, 0x97 }, + { 0x28, 0x29, 0xcc, 0x04, 0x80, 0xf3, 0xaf, 0xef, 0xb8, 0x44, 0xea, 0x17, 0x7b, 0x25, 0xd3, 0x60, 0xd6, 0x5c, 0xd3, 0x17, 0x0e, 0xc5, 0xbd, 0x3a, 0x4b, 0xb9, 0x7b, 0x9c, 0x6e, 0xbe, 0x0f, 0xc8 }, + { 0x34, 0xf3, 0x3b, 0x9a, 0x20, 0x09, 0x78, 0x1c, 0x6c, 0x6f, 0x05, 0x3c, 0x6a, 0x94, 0xf8, 0x17, 0xab, 0x8f, 0xda, 0xd9, 0x1a, 0x8c, 0x8e, 0x4f, 0x8c, 0x49, 0x87, 0xa6, 0xb3, 0xf0, 0x6f, 0xc8 }, + { 0x62, 0xb7, 0x84, 0x4f, 0xa7, 0xb7, 0x01, 0xdf, 0x83, 0x0f, 0x6b, 0x76, 0x72, 0x76, 0x0b, 0xc8, 0x91, 0x05, 0x46, 0x76, 0x0e, 0x8b, 0x0e, 0xcf, 0x8f, 0x69, 0x36, 0x56, 0xa8, 0x12, 0x0b, 0x5b }, + { 0x93, 0x30, 0x76, 0x10, 0xfa, 0x9e, 0x6f, 0x45, 0x1c, 0xe0, 0xb8, 0x7c, 0x8b, 0x03, 0x20, 0xb7, 0x46, 0x49, 0x51, 0x5e, 0x86, 0xb9, 0x04, 0xa7, 0xeb, 0x3d, 0x43, 0xa7, 0xf6, 0xa6, 0x50, 0x43 }, + { 0x4c, 0x90, 0x64, 0xf5, 0x57, 0xd7, 0x08, 0x96, 0x7b, 0x59, 0x94, 0x83, 0x3e, 0x6f, 0x4b, 0xd7, 0x43, 0x76, 0xd9, 0xb4, 0xee, 0xa1, 0xf9, 0xdd, 0xe5, 0x5e, 0x9a, 0x4d, 0xb8, 0xf9, 0x3d, 0xc7 }, + { 0x13, 0x69, 0xc1, 0x8c, 0x24, 0x33, 0x94, 0x77, 0x5f, 0x64, 0x88, 0x53, 0x2c, 0x3d, 0x9a, 0xec, 0xcc, 0xb2, 0x05, 0x1d, 0x49, 0x6c, 0x3b, 0x7e, 0xeb, 0xc8, 0xa2, 0x4d, 0x6c, 0x0e, 0xa8, 0xfa }, + { 0xd2, 0x92, 0x6f, 0x1e, 0x75, 0x3f, 0x93, 0x34, 0x7a, 0x89, 0xb2, 0xe9, 0x12, 0x0c, 0xc6, 0x35, 0xff, 0x2a, 0x40, 0xe9, 0xe8, 0xaf, 0x8a, 0xc1, 0x2e, 0xdc, 0x6e, 0x3c, 0x72, 0x98, 0xd9, 0x88 }, + { 0xa0, 0xbc, 0x2d, 0x5b, 0x29, 0x3e, 0x68, 0xbe, 0x4c, 0x92, 0x19, 0xe7, 0x8b, 0xc3, 0xec, 0xed, 0x6c, 0xbd, 0xaf, 0xfb, 0x5f, 0x83, 0x31, 0x34, 0x97, 0xc1, 0xe4, 0xa6, 0x24, 0x0e, 0x14, 0x47 }, + { 0x60, 0x61, 0x4a, 0xb9, 0x54, 0x7a, 0x38, 0xe8, 0x2b, 0xf9, 0x06, 0xd0, 0x8f, 0x07, 0x63, 0x15, 0xff, 0xed, 0xc7, 0x43, 0x2a, 0xaf, 0x29, 0xf4, 0x43, 0x81, 0xa5, 0xa4, 0xdd, 0x3e, 0xc7, 0xc3 }, + { 0x45, 0x46, 0x7c, 0xab, 0xe2, 0xd5, 0xa4, 0x61, 0xaa, 0x57, 0x86, 0xdd, 0xab, 0x02, 0x0c, 0xb0, 0x8d, 0x6d, 0x18, 0xc0, 0x79, 0x1c, 0xd0, 0x95, 0x88, 0x07, 0xf1, 0xb0, 0x9d, 0x8a, 0xe7, 0xbf }, + { 0xce, 0x05, 0xf3, 0x64, 0x00, 0x61, 0x55, 0xdc, 0xbf, 0x6b, 0xd4, 0x30, 0x4e, 0x0c, 0x4c, 0xcf, 0x85, 0x2a, 0x81, 0xf4, 0xd7, 0x04, 0xa2, 0x78, 0x49, 0x20, 0xb6, 0x20, 0x91, 0xeb, 0x0c, 0x99 }, + { 0xd2, 0xa0, 0x1e, 0x89, 0x5c, 0x8a, 0x03, 0x28, 0x16, 0x35, 0x08, 0x99, 0x3e, 0xe4, 0x74, 0xfc, 0x3f, 0x71, 0x79, 0xe7, 0x79, 0xe5, 0x34, 0xbd, 0x0c, 0x30, 0xcf, 0xd6, 0xfb, 0x7c, 0x47, 0xc0 }, + { 0x3e, 0x11, 0x06, 0x0c, 0xe6, 0xda, 0x16, 0x64, 0x7b, 0x3f, 0xcc, 0x47, 0xde, 0x57, 0xbe, 0x55, 0x6e, 0xe3, 0x31, 0x1c, 0x85, 0x97, 0xf9, 0x56, 0x15, 0x76, 0x76, 0xe4, 0x89, 0x85, 0x79, 0xe9 }, + { 0x08, 0x36, 0x36, 0x43, 0xad, 0xcd, 0x93, 0x99, 0x3b, 0x5d, 0x51, 0x9f, 0x04, 0x2b, 0x38, 0xae, 0x5a, 0xfb, 0x83, 0xe1, 0x65, 0x6c, 0x0c, 0xfd, 0x91, 0x4d, 0xeb, 0xfa, 0x2c, 0x47, 0x1b, 0x88 }, + { 0xa2, 0xe9, 0x2b, 0x55, 0x45, 0x9b, 0xfd, 0x25, 0xec, 0x0b, 0x63, 0x89, 0x4f, 0xcf, 0x32, 0xec, 0x7e, 0xdb, 0x1a, 0x2b, 0x3e, 0xc6, 0x95, 0xf7, 0x25, 0xfe, 0x15, 0xfc, 0x39, 0x3c, 0x8c, 0xe5 }, + { 0x1a, 0xdb, 0x43, 0x2b, 0xb7, 0x2c, 0x8a, 0x73, 0x15, 0x2d, 0x42, 0xc3, 0xf1, 0xf3, 0xd8, 0x8c, 0x78, 0x14, 0x07, 0xe4, 0x17, 0x8f, 0xad, 0xff, 0x9c, 0x1d, 0xfb, 0x0d, 0x95, 0xdc, 0x30, 0x37 }, + { 0x55, 0x66, 0x5c, 0xa6, 0xd6, 0x0c, 0xe9, 0xe7, 0x9f, 0x17, 0xf4, 0x15, 0x2a, 0xde, 0xdb, 0x00, 0x0f, 0x5c, 0x1b, 0xe4, 0x19, 0x06, 0x7e, 0x59, 0x2d, 0xaf, 0x2f, 0xfd, 0x9b, 0xab, 0xd4, 0x6c }, + { 0xe6, 0x30, 0xbe, 0x60, 0x42, 0x38, 0x9e, 0xd7, 0xb6, 0xdf, 0xb8, 0x3b, 0x23, 0x6a, 0xc7, 0x17, 0x59, 0x77, 0x15, 0xec, 0xa6, 0x64, 0xc2, 0x6e, 0x87, 0x22, 0x9a, 0x80, 0x71, 0x8b, 0x64, 0x26 }, + { 0x68, 0xe9, 0xb5, 0xf3, 0xbd, 0xba, 0x70, 0x88, 0x30, 0x87, 0xe1, 0x95, 0x28, 0x2f, 0xe6, 0x2c, 0x15, 0xca, 0x7c, 0xe8, 0x91, 0x7f, 0x17, 0xcf, 0xb2, 0x43, 0x56, 0x8f, 0xc6, 0xd0, 0xd1, 0x10 }, + { 0x4a, 0xa2, 0x36, 0xe9, 0xa6, 0x07, 0x54, 0xbf, 0xc4, 0x49, 0xde, 0x2a, 0x05, 0x08, 0x33, 0x5d, 0x17, 0xc1, 0x10, 0x3c, 0x15, 0xe2, 0xdb, 0x05, 0xd5, 0x08, 0x00, 0xca, 0x94, 0x39, 0x82, 0xba }, + { 0x1d, 0x93, 0x37, 0xdb, 0x37, 0x97, 0xbf, 0xd9, 0xde, 0xb1, 0xd2, 0xae, 0x5c, 0xbf, 0xbf, 0x05, 0xc8, 0x94, 0xff, 0x62, 0x6b, 0xcb, 0xd2, 0x01, 0x3b, 0x06, 0x43, 0x9d, 0x58, 0x8e, 0x26, 0x8a }, + { 0x8d, 0x8d, 0x7e, 0xbf, 0xbe, 0x02, 0x54, 0x92, 0x07, 0x0f, 0x1e, 0x3b, 0x2d, 0x2a, 0x29, 0x9d, 0x49, 0x3d, 0x20, 0xf4, 0xdb, 0x91, 0x08, 0xd6, 0x25, 0x46, 0x5a, 0x7a, 0x95, 0x5e, 0x09, 0x77 }, + { 0x02, 0xf6, 0x79, 0x9a, 0x15, 0x0f, 0xbe, 0x71, 0x47, 0x2a, 0x7d, 0x5f, 0x9b, 0x69, 0x89, 0x4b, 0x85, 0xa4, 0xa4, 0xf5, 0xfc, 0x5d, 0xbc, 0xce, 0xb6, 0xdd, 0x55, 0xce, 0x75, 0x6e, 0x12, 0x12 }, + { 0x97, 0xc1, 0x84, 0x0a, 0xdb, 0x7d, 0xca, 0x49, 0xfd, 0x50, 0x09, 0x78, 0xb4, 0x63, 0xd9, 0x90, 0xf3, 0x80, 0xf1, 0xf8, 0xa9, 0x5c, 0x4c, 0x50, 0x94, 0x16, 0x25, 0xe2, 0xba, 0x75, 0x61, 0xd1 }, + { 0x4e, 0xf6, 0x06, 0x09, 0x46, 0x52, 0xf0, 0x28, 0xa2, 0xa1, 0x40, 0xcc, 0x9c, 0xa0, 0xe1, 0x04, 0xe9, 0x1a, 0x39, 0x49, 0x51, 0x90, 0x08, 0xce, 0xc7, 0xf3, 0x67, 0x8e, 0xac, 0x18, 0xe1, 0x10 }, + { 0x35, 0xd0, 0xcf, 0x6b, 0x46, 0xa2, 0x7d, 0x24, 0x24, 0x36, 0x7f, 0xea, 0x6b, 0xda, 0xdb, 0xa7, 0xea, 0x77, 0xa5, 0x5c, 0xd3, 0x60, 0x52, 0xef, 0x57, 0xfc, 0x7d, 0x42, 0x7c, 0x20, 0xc7, 0xc9 }, + { 0x63, 0xef, 0x2d, 0x21, 0xd4, 0x37, 0xa7, 0xa9, 0xc8, 0x36, 0xf3, 0x17, 0xeb, 0x73, 0x4c, 0x4b, 0x77, 0xa6, 0x13, 0x8b, 0x5e, 0x8d, 0x44, 0x6d, 0x1e, 0x92, 0x15, 0x8b, 0x86, 0x67, 0xd2, 0x2b }, + { 0xf0, 0x1c, 0xe9, 0xd6, 0x79, 0xa9, 0x03, 0x36, 0xe1, 0xb5, 0x2f, 0x63, 0x2c, 0x61, 0x86, 0x24, 0xf2, 0x7a, 0xd1, 0x0c, 0xdf, 0x82, 0xbb, 0x27, 0x26, 0x2a, 0xe5, 0x51, 0x7c, 0x22, 0x29, 0x9c }, + { 0x81, 0x8e, 0x4d, 0x5e, 0x48, 0x28, 0x22, 0x49, 0xa1, 0x83, 0x9f, 0xf5, 0x02, 0x6b, 0x56, 0x90, 0x22, 0xd3, 0x3a, 0xca, 0x93, 0x40, 0x9f, 0x19, 0x6d, 0x80, 0xd1, 0x42, 0x33, 0x8f, 0x95, 0x09 }, + { 0x01, 0xa8, 0x72, 0x04, 0xe0, 0x40, 0x6e, 0x76, 0x7a, 0x42, 0xa5, 0x55, 0xd3, 0x2c, 0xd6, 0x01, 0x5e, 0x68, 0x62, 0xce, 0xca, 0x8a, 0xcf, 0x3d, 0xa1, 0x90, 0x2b, 0x49, 0xdb, 0x2d, 0x4e, 0x74 }, + { 0x2c, 0x06, 0x2a, 0x91, 0x37, 0x36, 0x59, 0x26, 0xac, 0xcf, 0xd0, 0x60, 0x79, 0xf8, 0x16, 0x56, 0xdc, 0x93, 0x68, 0xdc, 0x57, 0xeb, 0x8a, 0x23, 0xfd, 0x66, 0xba, 0x48, 0xbf, 0x22, 0xaa, 0xd9 }, + { 0xc1, 0x4e, 0xc2, 0xfb, 0x0f, 0x47, 0x63, 0x58, 0x57, 0x32, 0x37, 0xee, 0x17, 0xd0, 0x52, 0x4a, 0x34, 0x68, 0xbb, 0x33, 0xc8, 0x55, 0xe4, 0xcc, 0x5f, 0x8d, 0x4a, 0x8c, 0xd0, 0xf9, 0x02, 0xc1 }, + { 0x5a, 0x2d, 0x95, 0xe5, 0xa2, 0x42, 0x6c, 0xde, 0x0c, 0x46, 0x0a, 0xfc, 0x3a, 0x1a, 0x6d, 0x20, 0x7b, 0x20, 0xfd, 0x06, 0x29, 0x43, 0x08, 0xa8, 0x82, 0x53, 0x66, 0x49, 0x61, 0x48, 0xb1, 0x0b }, + { 0x4c, 0x67, 0xd0, 0xcd, 0x40, 0xf5, 0x3f, 0x11, 0x8a, 0x1e, 0xed, 0x46, 0xef, 0x38, 0x2f, 0x85, 0x8f, 0x94, 0xf7, 0x53, 0xac, 0x2d, 0xf1, 0x3c, 0x73, 0x6b, 0x73, 0xae, 0x8f, 0xb6, 0x0d, 0x55 }, + { 0x3e, 0xcb, 0x0d, 0xea, 0x27, 0x71, 0xfe, 0xf1, 0x51, 0x74, 0xa5, 0x70, 0x88, 0x50, 0xae, 0x10, 0xbf, 0x5c, 0x34, 0x9f, 0x29, 0xd4, 0x8b, 0xfc, 0x43, 0x01, 0x42, 0x65, 0x37, 0x60, 0x71, 0x45 }, + { 0x2b, 0xa6, 0x34, 0x97, 0x1d, 0xe5, 0x11, 0xc8, 0x86, 0xe3, 0xca, 0x9c, 0x6c, 0x3a, 0xcd, 0x09, 0x4b, 0x34, 0xa7, 0x8c, 0x2e, 0xcc, 0x9a, 0x72, 0x95, 0xd7, 0xed, 0x78, 0x50, 0x2e, 0x78, 0xc8 }, + { 0x69, 0x37, 0xe8, 0x14, 0xe2, 0x41, 0xf4, 0x7c, 0x9c, 0xa1, 0xb4, 0xe9, 0xe4, 0xb1, 0x2a, 0x12, 0xba, 0x48, 0x4a, 0xd3, 0xb1, 0xf3, 0x42, 0x52, 0xd0, 0x53, 0xfe, 0x29, 0x32, 0x25, 0x73, 0x9c }, + { 0x5b, 0x42, 0x81, 0xde, 0x5e, 0x57, 0x3b, 0x93, 0x9f, 0x43, 0x04, 0x26, 0x60, 0x02, 0xa7, 0x16, 0xf5, 0x1f, 0x20, 0x16, 0x9d, 0x18, 0xca, 0x81, 0xeb, 0x38, 0x56, 0x68, 0x54, 0x3c, 0xc6, 0xcd }, + { 0x89, 0x98, 0xca, 0xfc, 0x82, 0xcb, 0x0a, 0x05, 0x0d, 0x46, 0xc6, 0x15, 0xda, 0xea, 0x3e, 0x87, 0x5b, 0x88, 0x16, 0x19, 0xf5, 0x96, 0x21, 0x44, 0x47, 0x96, 0x28, 0x3e, 0x43, 0x33, 0x13, 0x5c }, + { 0xff, 0x55, 0xed, 0x41, 0xd7, 0x3f, 0x62, 0xf8, 0x53, 0x0a, 0x35, 0xe2, 0x4c, 0x20, 0x9c, 0x6a, 0xe5, 0x82, 0x65, 0x0c, 0x1c, 0x81, 0x25, 0x1a, 0x0a, 0x59, 0xa5, 0x2f, 0x2f, 0x0d, 0xad, 0xec }, + { 0x5b, 0x8a, 0xc1, 0x7d, 0xdd, 0xe9, 0x0c, 0xbc, 0x6e, 0xad, 0xb9, 0x6d, 0x11, 0x3c, 0x06, 0x96, 0xf0, 0x39, 0xfb, 0x77, 0x11, 0x45, 0x8e, 0x7a, 0x5c, 0xea, 0x95, 0xb0, 0x72, 0x10, 0xa8, 0xbf }, + { 0x47, 0x27, 0xc4, 0xc1, 0x63, 0xc7, 0x59, 0xb7, 0xbc, 0xee, 0x2e, 0x68, 0x34, 0xaa, 0x3d, 0x83, 0xe7, 0xd1, 0x12, 0x73, 0x50, 0x45, 0xb5, 0x1e, 0x5d, 0x28, 0x28, 0x82, 0x54, 0xad, 0x67, 0x92 }, + { 0x6e, 0x28, 0xcc, 0x6d, 0x75, 0x23, 0x26, 0xb9, 0x84, 0x4c, 0x9e, 0x28, 0x74, 0xb7, 0x45, 0xda, 0x8e, 0xee, 0x81, 0x85, 0x33, 0x96, 0x1a, 0x8a, 0xb5, 0x59, 0xcf, 0xed, 0x1a, 0xb9, 0x24, 0x42 }, + { 0xd7, 0x83, 0x22, 0x20, 0x59, 0xe8, 0xa4, 0x9c, 0x05, 0xc5, 0x45, 0xb7, 0x12, 0x43, 0x4f, 0x99, 0xd0, 0x7c, 0xd5, 0x6d, 0xd9, 0x6b, 0xc4, 0x02, 0x1d, 0x72, 0x61, 0x61, 0x10, 0x85, 0x94, 0x9f }, + { 0x53, 0x4f, 0x53, 0x7d, 0xa6, 0x33, 0x59, 0xc8, 0xb0, 0xde, 0x86, 0xf1, 0x53, 0xcc, 0x7e, 0xe4, 0x8c, 0x72, 0xaf, 0x9a, 0xe2, 0x80, 0x21, 0x93, 0xfa, 0xcf, 0x50, 0x48, 0x6f, 0xf4, 0x26, 0xd5 }, + { 0xb2, 0x6c, 0x74, 0x7a, 0x0f, 0x3e, 0xc7, 0x7c, 0xd1, 0x5c, 0x6e, 0x9b, 0x79, 0x37, 0x9d, 0xa5, 0xae, 0xa9, 0xdc, 0x30, 0x59, 0x7f, 0xc0, 0x77, 0xd2, 0xd1, 0x24, 0x75, 0x37, 0xa1, 0x3d, 0x68 }, + { 0xa2, 0xbb, 0x47, 0xad, 0x48, 0x19, 0x66, 0x36, 0xa9, 0x16, 0x3e, 0x05, 0xdc, 0x9f, 0x45, 0xe7, 0xf5, 0x25, 0x5c, 0xbb, 0x82, 0x24, 0xa0, 0x01, 0x12, 0xa5, 0x0e, 0xfd, 0x8d, 0xb6, 0xac, 0x0e }, + { 0x24, 0xfe, 0x81, 0xf9, 0xf7, 0x38, 0x3f, 0x38, 0x41, 0x27, 0x4d, 0x50, 0xfc, 0xa3, 0x46, 0xde, 0xce, 0x5b, 0x76, 0x7f, 0xbe, 0x42, 0xac, 0xc6, 0x19, 0x1c, 0xd1, 0x05, 0x3d, 0x93, 0x4f, 0x33 }, + { 0xcf, 0x0d, 0x0c, 0xb9, 0xeb, 0xc2, 0xaf, 0x2e, 0xcd, 0xf2, 0x5e, 0xb2, 0xdb, 0xe5, 0x5b, 0x10, 0x78, 0xa8, 0x13, 0x6b, 0xfc, 0x9d, 0xb7, 0x6f, 0xc3, 0x4a, 0x00, 0x10, 0x44, 0xf6, 0x57, 0x0e }, + { 0x08, 0xbb, 0x99, 0x00, 0x78, 0xe5, 0x99, 0xd7, 0xf2, 0x71, 0x4c, 0x60, 0x6f, 0xeb, 0x37, 0xc2, 0x21, 0xb0, 0xc0, 0xa3, 0x4f, 0x23, 0xb7, 0xe8, 0xf6, 0x34, 0x14, 0x52, 0x73, 0x8c, 0xdb, 0xbe }, + { 0xfa, 0x3b, 0xff, 0xb8, 0x03, 0x14, 0x9a, 0x7b, 0xee, 0x77, 0x43, 0x02, 0x6c, 0xb6, 0x62, 0x98, 0xd3, 0xf7, 0x35, 0xd6, 0x53, 0xf4, 0x8f, 0x55, 0xde, 0x96, 0x7c, 0x40, 0xcd, 0x68, 0x5d, 0x3b }, + { 0x42, 0xa3, 0xf1, 0xa1, 0x9c, 0x5e, 0x7f, 0x03, 0x8d, 0xed, 0x17, 0xab, 0x28, 0x9b, 0x58, 0x2b, 0x4d, 0x39, 0x6b, 0x49, 0xcc, 0x75, 0xac, 0xed, 0x30, 0x16, 0xd8, 0x94, 0xc9, 0xb2, 0xd3, 0x22 }, + { 0x79, 0xa8, 0x5c, 0x15, 0x71, 0xf9, 0x0a, 0x22, 0x75, 0x48, 0x44, 0xdd, 0xe4, 0x6e, 0xf0, 0x54, 0xe6, 0x43, 0x56, 0x7f, 0xcd, 0x0d, 0x4f, 0x40, 0x26, 0xf9, 0x0b, 0x3c, 0xf5, 0x0b, 0xbf, 0x63 }, + { 0x26, 0x48, 0xad, 0x0d, 0x31, 0x03, 0xe1, 0x85, 0x3f, 0x7c, 0x3a, 0xf7, 0x3e, 0xe4, 0x6e, 0xa7, 0x19, 0x93, 0xbf, 0xce, 0x05, 0xd7, 0xb6, 0x38, 0xa3, 0x30, 0x63, 0xf9, 0x24, 0x39, 0xb3, 0x91 }, + { 0xe8, 0xb5, 0x92, 0x66, 0xc1, 0xe5, 0xc7, 0x05, 0x83, 0xc5, 0xa2, 0xdb, 0x56, 0x72, 0x24, 0xa7, 0x63, 0x72, 0x8b, 0x23, 0xb4, 0xf5, 0xdd, 0xa7, 0x75, 0x11, 0x96, 0xd5, 0x70, 0xb4, 0x8c, 0xe4 }, + { 0x7e, 0x12, 0xbb, 0x3f, 0x8e, 0x52, 0x6b, 0x40, 0xe6, 0x98, 0x15, 0x56, 0x6d, 0xba, 0x6c, 0x35, 0xae, 0xe0, 0x80, 0xcf, 0xa2, 0xb5, 0xe5, 0xe0, 0xbb, 0x80, 0x4f, 0x31, 0xf8, 0xf0, 0xec, 0x3b }, + { 0xa5, 0x6d, 0x8b, 0x27, 0xa6, 0xbe, 0xb4, 0x99, 0x9a, 0x24, 0xd3, 0x7d, 0x39, 0x4b, 0xf3, 0x3d, 0xb7, 0x99, 0xc5, 0xd3, 0x8d, 0xdc, 0xb1, 0x51, 0x4e, 0x22, 0xbd, 0x62, 0x40, 0xfb, 0x30, 0x66 }, + { 0xb9, 0xa6, 0x03, 0x0a, 0xd4, 0xc0, 0x5a, 0x95, 0xae, 0x0f, 0xf0, 0x28, 0x09, 0xb2, 0xfe, 0x9c, 0x6e, 0x6d, 0x71, 0xc2, 0x19, 0x98, 0xc1, 0x77, 0xf3, 0x84, 0x2c, 0x98, 0x6b, 0xd1, 0x9a, 0x41 }, + { 0x61, 0x95, 0x64, 0xdd, 0xfc, 0x78, 0x52, 0x0e, 0x70, 0x48, 0x2c, 0x77, 0x5d, 0xa5, 0x04, 0x36, 0x74, 0x41, 0x99, 0xf7, 0xbc, 0x95, 0x3c, 0xfe, 0xa8, 0x60, 0x2d, 0x67, 0x6d, 0x88, 0xb8, 0xad }, + { 0x02, 0x10, 0x97, 0xd4, 0xcc, 0x00, 0x13, 0x39, 0x6b, 0x8e, 0xe3, 0x82, 0x06, 0x16, 0x79, 0x25, 0x2e, 0x23, 0x7e, 0x7f, 0x47, 0x90, 0x2b, 0xa4, 0x21, 0x11, 0x6f, 0x4b, 0x01, 0x48, 0x8a, 0xd7 }, + { 0x2c, 0x1f, 0x2c, 0xac, 0x92, 0xf5, 0x44, 0x3a, 0x28, 0x05, 0x93, 0xc1, 0x6c, 0x2c, 0x45, 0x16, 0x0b, 0x25, 0x19, 0xdd, 0x7a, 0x2a, 0xb2, 0xa6, 0x00, 0x68, 0xf0, 0xcf, 0xff, 0x58, 0x28, 0xe1 }, + { 0x29, 0xb5, 0x61, 0x35, 0x68, 0x11, 0x6e, 0x6e, 0x76, 0x67, 0x39, 0x3c, 0xcc, 0x81, 0x90, 0x33, 0x9d, 0xf3, 0xd0, 0x4a, 0x7f, 0xb1, 0x5e, 0x03, 0xd5, 0x53, 0x99, 0x60, 0xfb, 0xd8, 0x0e, 0x62 }, + { 0x47, 0x2e, 0x17, 0x63, 0xa2, 0x49, 0x91, 0xab, 0x3e, 0x66, 0x28, 0x37, 0xa3, 0x9a, 0x0c, 0x4b, 0x0f, 0x43, 0x17, 0xbd, 0x81, 0x96, 0xd7, 0xe4, 0x2a, 0xec, 0x8b, 0xe8, 0x07, 0x65, 0x37, 0x5b }, + { 0x45, 0x03, 0xfa, 0x6d, 0xba, 0xc5, 0xb7, 0xc9, 0x1b, 0xc7, 0x3b, 0x26, 0xb8, 0xe7, 0x94, 0x7c, 0x1d, 0x2a, 0xa4, 0x5d, 0x16, 0x0a, 0xf0, 0x4b, 0x45, 0xaa, 0x85, 0x08, 0x90, 0x21, 0x55, 0x34 }, + { 0xcf, 0x95, 0x39, 0xe0, 0xf4, 0x07, 0xef, 0xec, 0x6d, 0x3f, 0x6a, 0xe1, 0x6e, 0x2f, 0x08, 0xc3, 0x67, 0xc6, 0xcf, 0xd8, 0x58, 0xee, 0xc0, 0x14, 0xa3, 0x17, 0xfe, 0x58, 0x60, 0x6f, 0x9f, 0x7b }, + { 0x91, 0x54, 0x08, 0xef, 0xe1, 0xea, 0xd6, 0x59, 0xf9, 0xc7, 0x79, 0xd8, 0x85, 0x6d, 0x2b, 0x61, 0xa8, 0xc9, 0x66, 0x96, 0xe5, 0x43, 0x97, 0xb1, 0x84, 0x0c, 0xa0, 0xef, 0x7c, 0x4d, 0xaa, 0x6b }, + { 0xf5, 0x27, 0x2b, 0x6e, 0xc6, 0xfe, 0xb0, 0x43, 0x87, 0xb6, 0xdc, 0x13, 0xc3, 0x97, 0x45, 0xff, 0x45, 0xf8, 0x1f, 0x5a, 0x73, 0x3e, 0xd7, 0x1a, 0xdd, 0xd0, 0x4f, 0x45, 0xaf, 0x61, 0xb8, 0x4e }, + { 0xc7, 0x98, 0xa8, 0xc8, 0x56, 0x9d, 0x3e, 0xb0, 0xcb, 0x6d, 0x96, 0x17, 0xb9, 0xdc, 0x86, 0x62, 0x30, 0x96, 0xd9, 0x4d, 0xbf, 0xcb, 0xbe, 0xdd, 0xf5, 0x84, 0xb3, 0x00, 0x0f, 0x80, 0xed, 0x90 }, + { 0xa5, 0x95, 0xa2, 0x5e, 0xef, 0x49, 0x50, 0x70, 0xd2, 0xf0, 0x7f, 0x87, 0xe9, 0x4e, 0x42, 0x10, 0x6b, 0x3a, 0xcb, 0xa0, 0xf8, 0x36, 0x81, 0xb8, 0xcc, 0xbf, 0x54, 0x3a, 0x05, 0x8c, 0x3a, 0x6f }, + { 0xfd, 0x81, 0xa1, 0x03, 0x5c, 0xe1, 0x63, 0xf8, 0x3d, 0xa1, 0x88, 0xe8, 0xd2, 0xab, 0x9b, 0xe7, 0x47, 0x2f, 0x64, 0x9c, 0x68, 0xcb, 0x01, 0x25, 0x28, 0xc9, 0x7d, 0xf4, 0x2c, 0x70, 0x02, 0x58 }, + { 0xfa, 0x67, 0xcb, 0xd4, 0xf3, 0x7c, 0xac, 0xac, 0x4d, 0x8c, 0xc6, 0x21, 0x58, 0x00, 0x7f, 0x26, 0x86, 0x2d, 0xf4, 0xc1, 0x9c, 0xa9, 0x0d, 0x9c, 0xea, 0xe4, 0x16, 0x74, 0x8a, 0xa9, 0x3b, 0x83 }, + { 0xc6, 0x21, 0x75, 0x63, 0xfb, 0xda, 0xb3, 0x90, 0x01, 0x30, 0x85, 0xb9, 0x4f, 0xe8, 0x24, 0x11, 0xa6, 0x08, 0x90, 0xd7, 0x1a, 0x6c, 0x96, 0xbc, 0x0b, 0x94, 0xad, 0xb7, 0x62, 0xd1, 0xaa, 0xdd }, + { 0x91, 0xc5, 0x89, 0xb1, 0xa2, 0xfd, 0xf8, 0xc8, 0x81, 0x57, 0xbb, 0x40, 0xee, 0x33, 0x1a, 0x53, 0x83, 0x7f, 0xbd, 0x7e, 0x9a, 0xae, 0xf1, 0x31, 0x5c, 0x23, 0x17, 0xd8, 0x7b, 0xf9, 0x1f, 0xb2 }, + { 0x45, 0x5b, 0x89, 0xef, 0xad, 0x62, 0x68, 0xca, 0xfa, 0x88, 0x56, 0xd7, 0x77, 0x4a, 0xb2, 0x48, 0xfa, 0x20, 0x9b, 0x30, 0xa2, 0x34, 0xa9, 0x5c, 0x38, 0x63, 0x10, 0x36, 0x07, 0x5f, 0x8b, 0x28 }, + { 0x90, 0xf0, 0xfc, 0x59, 0xdd, 0x1a, 0x83, 0xa1, 0x5b, 0x73, 0xf9, 0xb3, 0xa8, 0xc2, 0x00, 0x94, 0xd1, 0x79, 0xb5, 0x86, 0x38, 0xc4, 0x59, 0x27, 0xd9, 0xb6, 0xcc, 0x72, 0x81, 0x74, 0x18, 0xdf }, + { 0x4f, 0xca, 0xaf, 0xad, 0xc9, 0xab, 0x37, 0x77, 0x49, 0x2a, 0x1d, 0xd2, 0xfe, 0x83, 0xd5, 0x25, 0xac, 0x6b, 0xa6, 0x3c, 0xe9, 0x60, 0x3f, 0x3a, 0x7a, 0xe3, 0x03, 0xa6, 0xfd, 0xf8, 0xf1, 0x08 }, + { 0x9d, 0x43, 0x8f, 0x91, 0x87, 0xf8, 0x53, 0xf4, 0xc1, 0x25, 0x8a, 0x56, 0x55, 0x66, 0x6e, 0x27, 0x1d, 0x5d, 0xfb, 0x9d, 0x1d, 0x99, 0x81, 0x93, 0x52, 0x4b, 0xf2, 0xb5, 0x57, 0x9b, 0x57, 0xf2 }, + { 0x19, 0x81, 0x61, 0xe8, 0xad, 0x72, 0x89, 0x07, 0x48, 0xef, 0x90, 0x42, 0x59, 0x0a, 0xf0, 0xc4, 0x93, 0xd2, 0x84, 0x8c, 0x62, 0x68, 0xe8, 0x79, 0xda, 0x6f, 0x8f, 0x90, 0xfc, 0xd9, 0x97, 0xaa }, + { 0xa4, 0x42, 0xe6, 0xfb, 0xc8, 0xe9, 0x39, 0xe9, 0x3a, 0x36, 0xad, 0x6a, 0xff, 0xa2, 0xf3, 0xbc, 0xb2, 0x9d, 0x02, 0x5d, 0xa8, 0xd1, 0xa2, 0xa3, 0x5f, 0x79, 0xd1, 0x20, 0x25, 0xc8, 0xea, 0xd3 }, + { 0x66, 0xa0, 0xd2, 0x19, 0xcb, 0xff, 0x73, 0x3e, 0x74, 0x5b, 0x66, 0xe1, 0xd1, 0xf7, 0xe9, 0x60, 0xc2, 0x2b, 0x44, 0x2d, 0xe2, 0xb9, 0xc2, 0x0c, 0xb4, 0xd5, 0xd4, 0xb3, 0x65, 0xc5, 0x52, 0x70 }, + { 0x26, 0x37, 0x25, 0x41, 0xcf, 0x75, 0x8e, 0x30, 0x4e, 0x5d, 0xb7, 0x6f, 0x2e, 0x49, 0xdd, 0xad, 0xad, 0x88, 0xca, 0x23, 0x01, 0x42, 0xf2, 0xa3, 0x0e, 0xd4, 0x52, 0x2c, 0xec, 0x14, 0x9c, 0x80 }, + { 0x83, 0x36, 0xbe, 0x85, 0x39, 0x14, 0xcb, 0x01, 0x9a, 0x39, 0x19, 0xf1, 0x3c, 0x67, 0xb6, 0x9e, 0x28, 0xcb, 0x71, 0xe4, 0xb7, 0x33, 0xa7, 0xdb, 0x68, 0xea, 0xa1, 0x67, 0x4d, 0x4d, 0xf7, 0xe0 }, + { 0x2f, 0x77, 0xa0, 0x5b, 0xea, 0xa7, 0x34, 0xe9, 0x3d, 0x8b, 0xf5, 0xc8, 0x0d, 0x50, 0xbf, 0x25, 0xd1, 0xe4, 0xfb, 0x86, 0xe7, 0x88, 0x25, 0x6b, 0x17, 0xe8, 0x03, 0x53, 0x96, 0x60, 0x6c, 0x46 }, + { 0x40, 0x35, 0x13, 0x82, 0xa6, 0x4d, 0x47, 0x14, 0x55, 0xf8, 0x26, 0x2a, 0xd3, 0x75, 0x36, 0x2a, 0x84, 0xf5, 0x0a, 0x1f, 0x62, 0xc0, 0x5a, 0xcf, 0xdb, 0x94, 0x85, 0x44, 0xc6, 0x92, 0x0b, 0xa7 }, + { 0x31, 0x49, 0x45, 0x5e, 0x13, 0x16, 0xe5, 0xc2, 0x38, 0x9a, 0x32, 0x02, 0xf2, 0xd0, 0xc1, 0xe3, 0xcf, 0x65, 0x96, 0x27, 0xce, 0xe2, 0x46, 0xec, 0x7d, 0xfe, 0xeb, 0x0b, 0x97, 0xa9, 0x5d, 0x4e }, + { 0x9b, 0xb5, 0x72, 0x56, 0x51, 0x15, 0x05, 0xf9, 0x80, 0xf5, 0xa2, 0xb3, 0x5e, 0xb9, 0x15, 0x92, 0x5a, 0x63, 0xda, 0xee, 0x57, 0x51, 0xb5, 0x4a, 0xb7, 0xa5, 0x24, 0x60, 0x3e, 0x39, 0x44, 0xb3 }, + { 0x08, 0x08, 0xf1, 0xe8, 0xba, 0xb3, 0x4a, 0xd8, 0x7e, 0xb4, 0x7f, 0x70, 0xe1, 0x45, 0x6b, 0x95, 0x71, 0x52, 0x6e, 0x80, 0xf1, 0x76, 0x01, 0xe9, 0xce, 0xb9, 0xb9, 0x23, 0x08, 0x93, 0xaf, 0xb0 }, + { 0x85, 0x36, 0x7a, 0x37, 0x6a, 0x43, 0xff, 0x71, 0x05, 0xcc, 0xf2, 0xa9, 0x64, 0xd5, 0xf7, 0xe2, 0x22, 0xac, 0x83, 0xd0, 0xe4, 0xaa, 0xcb, 0x0b, 0xfb, 0xe3, 0x8f, 0xaf, 0x84, 0xb4, 0xb6, 0x6d }, + { 0xf1, 0x14, 0xf0, 0xa9, 0x7f, 0x99, 0x95, 0x65, 0x86, 0x60, 0x74, 0x46, 0x6a, 0xc7, 0x7e, 0xd1, 0x13, 0xf9, 0x58, 0x9b, 0x6c, 0x90, 0xc5, 0x3f, 0x23, 0x1d, 0xdb, 0xd0, 0x4d, 0x1b, 0xd5, 0x25 }, + { 0x4f, 0x74, 0xc9, 0x80, 0x51, 0xf9, 0xd1, 0x2f, 0x23, 0xf0, 0x75, 0x0b, 0xac, 0x07, 0x13, 0xff, 0x53, 0xe5, 0x8b, 0xdf, 0x57, 0x74, 0x10, 0x34, 0xb7, 0x42, 0xc6, 0x05, 0x87, 0xe9, 0x36, 0x6c }, + { 0xac, 0x87, 0xe1, 0xc0, 0xc7, 0x6f, 0xf7, 0x6e, 0x2f, 0xa0, 0x84, 0x9f, 0x0d, 0x07, 0x9d, 0xf6, 0xe8, 0xf1, 0x1f, 0x2c, 0xa4, 0x42, 0x35, 0x8d, 0x6c, 0x7b, 0x06, 0xe9, 0xb4, 0xdf, 0xcf, 0x65 }, + { 0xeb, 0x5c, 0xa7, 0x24, 0x18, 0x85, 0x99, 0x11, 0x69, 0xe8, 0x50, 0xca, 0x1d, 0x22, 0xec, 0xab, 0xe4, 0xf0, 0x42, 0x67, 0x09, 0xec, 0x91, 0x5d, 0x86, 0x39, 0xa4, 0x83, 0xcd, 0xe3, 0xa4, 0xb5 }, + { 0xc2, 0x47, 0x4e, 0xef, 0xd6, 0xd4, 0x37, 0x4b, 0x22, 0x7f, 0x39, 0x8a, 0x0c, 0xf7, 0x46, 0xa3, 0x3e, 0xb3, 0x0e, 0xbe, 0x02, 0xec, 0x98, 0xdb, 0x10, 0xfb, 0xd7, 0x46, 0xd3, 0x91, 0x65, 0xe8 }, + { 0x06, 0x38, 0x87, 0xc6, 0xfe, 0x24, 0x9b, 0xdc, 0x03, 0x66, 0xd9, 0x56, 0xc0, 0xf6, 0x72, 0x13, 0xb9, 0x63, 0xd2, 0x0b, 0x2d, 0x45, 0xd4, 0x37, 0xca, 0x05, 0xd0, 0xf0, 0x33, 0x36, 0x68, 0x24 }, + { 0x14, 0xf7, 0x9e, 0x44, 0x5b, 0x93, 0xc2, 0x4a, 0x14, 0x0d, 0xbc, 0x99, 0x0c, 0x98, 0x76, 0xc0, 0xc4, 0xfc, 0xe8, 0x82, 0x59, 0xd2, 0x13, 0xe6, 0x1e, 0x70, 0x4e, 0x76, 0x30, 0xd0, 0xfa, 0x09 }, + { 0x5f, 0xce, 0x6e, 0x31, 0x36, 0xca, 0x3c, 0xa4, 0x33, 0xae, 0xe5, 0xb2, 0x63, 0x4c, 0xae, 0xb8, 0xcf, 0x00, 0x3e, 0x5f, 0x79, 0x9f, 0x2f, 0x8b, 0xb9, 0x2c, 0xf2, 0xcd, 0x65, 0x66, 0x2f, 0x7b }, + { 0x04, 0xdc, 0x53, 0x25, 0x41, 0x8a, 0x50, 0x9a, 0x33, 0x52, 0xea, 0xca, 0x5a, 0x8d, 0x74, 0xa6, 0x1e, 0x83, 0xef, 0x78, 0xaa, 0x8e, 0x60, 0x9b, 0xbb, 0x3e, 0xdd, 0x47, 0x97, 0x9f, 0xb9, 0xdc }, + { 0x63, 0xa4, 0x2e, 0x76, 0x07, 0x84, 0x80, 0xe9, 0xc4, 0x04, 0xaf, 0x5f, 0x99, 0x84, 0x3c, 0xc5, 0x08, 0x2c, 0xff, 0xf3, 0x0a, 0x5f, 0x11, 0x3b, 0x07, 0x58, 0xa0, 0xca, 0xd1, 0xeb, 0x3b, 0x70 }, + { 0x9a, 0x16, 0xbf, 0x59, 0xb2, 0x76, 0x72, 0x05, 0x90, 0xec, 0x74, 0x52, 0x74, 0x0c, 0x88, 0xe0, 0x18, 0x4f, 0x16, 0x49, 0x55, 0xf0, 0x46, 0xea, 0xaf, 0x6e, 0x8e, 0x45, 0x1b, 0x6c, 0x43, 0x82 }, + { 0xe4, 0x34, 0x64, 0xac, 0x10, 0xfc, 0x0a, 0xa6, 0xf6, 0x32, 0xc2, 0x81, 0xcd, 0x3a, 0xae, 0x9e, 0xa2, 0x57, 0xfc, 0x4b, 0x4f, 0xd1, 0x5a, 0xbb, 0x9e, 0x04, 0x4e, 0x04, 0x1c, 0x81, 0xbb, 0x92 }, + { 0x43, 0xe0, 0x0c, 0xfc, 0x9e, 0x3a, 0x1e, 0x4f, 0xd3, 0x0e, 0x2a, 0x5c, 0x5a, 0xb9, 0x33, 0x0d, 0x1b, 0xd7, 0x21, 0x6c, 0xdc, 0x03, 0x8c, 0x36, 0x8f, 0xe4, 0x9a, 0x87, 0x02, 0xcf, 0x65, 0xa7 }, + { 0xf7, 0x8f, 0x95, 0x09, 0xf1, 0xce, 0x8c, 0xbb, 0x3f, 0xc0, 0xb7, 0x14, 0x00, 0xdc, 0x5c, 0xd0, 0xe4, 0x1c, 0x56, 0x89, 0x53, 0xf4, 0x9a, 0x8a, 0xa4, 0xc7, 0xbc, 0xc5, 0xfe, 0x64, 0x80, 0x9e }, + { 0x08, 0xdd, 0xc5, 0x25, 0xf7, 0xc5, 0x0b, 0xaa, 0x7c, 0xf2, 0xc9, 0xd6, 0x9b, 0xb7, 0x0e, 0xf4, 0x57, 0x49, 0x13, 0xf3, 0x4f, 0x38, 0x6e, 0xeb, 0x4b, 0xcc, 0x20, 0xf7, 0x5d, 0x12, 0x26, 0xd2 }, + { 0x95, 0xff, 0xe0, 0x8d, 0x7f, 0xc5, 0x06, 0x6d, 0xc3, 0x5b, 0xa4, 0x09, 0x81, 0xa7, 0xc6, 0x34, 0x8b, 0x99, 0x09, 0x32, 0xb7, 0x9c, 0x38, 0x07, 0xc9, 0x1b, 0x28, 0xa7, 0x1b, 0x31, 0x4e, 0xb9 }, + { 0x02, 0xd3, 0x98, 0xe6, 0xa5, 0xad, 0x51, 0x3c, 0xed, 0xe1, 0x27, 0xc2, 0xc4, 0x7a, 0xf3, 0x96, 0xa1, 0x04, 0x10, 0x43, 0x90, 0x52, 0xef, 0xd5, 0xba, 0x2e, 0x36, 0x7b, 0xb7, 0x82, 0x51, 0xa5 }, + { 0x32, 0x76, 0x61, 0x83, 0x99, 0x18, 0x20, 0x5c, 0x16, 0x78, 0x72, 0xa0, 0xe4, 0xc3, 0x6c, 0x0a, 0x02, 0xe0, 0x50, 0xf8, 0x66, 0xc6, 0xd3, 0xc4, 0xa3, 0xfd, 0xca, 0xbf, 0x46, 0x54, 0x0d, 0xcf }, + { 0x50, 0xfb, 0x84, 0xaf, 0x6d, 0x95, 0x42, 0x7e, 0x5f, 0xa6, 0x19, 0x48, 0x72, 0x99, 0x33, 0xae, 0xe9, 0x25, 0x40, 0x50, 0xd9, 0x25, 0x9a, 0xb6, 0xa9, 0x2c, 0xbe, 0xed, 0xfb, 0x6a, 0x2d, 0x16 }, + { 0xa3, 0xf6, 0x4e, 0xbb, 0x4c, 0x02, 0x9b, 0x7a, 0x28, 0x07, 0x49, 0x76, 0x8b, 0x07, 0xa0, 0x9f, 0xcc, 0xbc, 0x04, 0xfd, 0x1d, 0xe4, 0x17, 0xca, 0x48, 0x9f, 0xdf, 0x66, 0x45, 0x73, 0xbc, 0x62 }, + { 0x7f, 0x82, 0x2f, 0x5c, 0x2e, 0xd3, 0xac, 0xe1, 0xad, 0xca, 0x13, 0x93, 0xb3, 0x22, 0x22, 0xd7, 0xf5, 0x02, 0xeb, 0xa7, 0xe0, 0x8a, 0x7e, 0xa3, 0x58, 0xfb, 0xe4, 0xc8, 0xc0, 0x07, 0xe5, 0xfd }, + { 0xa0, 0x27, 0x29, 0x73, 0x0e, 0xc1, 0x6a, 0x4c, 0xd7, 0x29, 0x7c, 0xa8, 0x23, 0x28, 0xf1, 0x5c, 0x8d, 0x73, 0x7c, 0x2f, 0x51, 0x16, 0xc0, 0x75, 0x63, 0x2f, 0x1d, 0x26, 0x9e, 0xac, 0xf2, 0x01 }, + { 0xff, 0x20, 0xc8, 0x1f, 0xb8, 0xd2, 0x60, 0xb5, 0xd3, 0xe2, 0x13, 0x11, 0x1d, 0x9d, 0x17, 0x72, 0xe2, 0x7f, 0xb6, 0xf3, 0x86, 0x1a, 0xe4, 0x06, 0x0a, 0xbc, 0x99, 0xa1, 0x73, 0x9b, 0xf2, 0x65 }, + { 0xf7, 0x51, 0x9a, 0x3a, 0x99, 0x6d, 0x19, 0x0b, 0xf5, 0xae, 0xaf, 0xaf, 0x1f, 0x9b, 0x9c, 0x0f, 0xac, 0xb0, 0x01, 0xad, 0x3f, 0x84, 0x98, 0x15, 0x4f, 0x53, 0x33, 0x04, 0x3a, 0x10, 0xcd, 0x68 }, + { 0x0c, 0xe9, 0xf0, 0x2d, 0xd2, 0xaa, 0xad, 0x06, 0x5d, 0xa1, 0x1c, 0x9b, 0xd5, 0x67, 0x14, 0x56, 0x43, 0x5b, 0x0a, 0xfc, 0x8a, 0x83, 0x9b, 0x16, 0x21, 0xe5, 0x94, 0xf7, 0x03, 0x51, 0x0d, 0x40 }, + { 0xb7, 0x86, 0x4a, 0xe5, 0x29, 0x3e, 0x3a, 0xa4, 0xd5, 0x2e, 0x97, 0x9e, 0x86, 0x96, 0x8e, 0xd0, 0x2b, 0x5c, 0x3b, 0x1d, 0x45, 0x45, 0x30, 0xf9, 0x7b, 0x81, 0x7e, 0x50, 0x72, 0x3a, 0x54, 0xfc }, + { 0x7e, 0x81, 0xef, 0x95, 0x2d, 0xd5, 0xec, 0x88, 0x0a, 0x49, 0xa4, 0xe5, 0x96, 0xaa, 0x2e, 0x4c, 0xb9, 0xba, 0xa8, 0xcc, 0x4b, 0xd5, 0xbf, 0x96, 0x4e, 0x4f, 0x7d, 0xbb, 0x2c, 0x68, 0x06, 0xca }, + { 0xa2, 0xaf, 0x78, 0xb0, 0x9a, 0xe0, 0xef, 0xf3, 0x11, 0x55, 0x88, 0xbf, 0xf9, 0x13, 0x74, 0x1d, 0xdf, 0xa3, 0x74, 0xfd, 0xff, 0xa5, 0xbe, 0x1f, 0x01, 0xc1, 0xf4, 0xf3, 0xb0, 0x8c, 0xd7, 0x60 }, + { 0x75, 0x06, 0x2a, 0x64, 0x34, 0x66, 0x24, 0xd2, 0xfd, 0xb2, 0x62, 0x51, 0x3a, 0xbc, 0xbc, 0x84, 0xf4, 0x1b, 0x07, 0xe6, 0x43, 0x76, 0x6e, 0x5b, 0x9e, 0x06, 0x8c, 0xa2, 0x2e, 0x85, 0xc4, 0x7b }, + { 0xe2, 0x91, 0x5d, 0xd2, 0xb6, 0x38, 0x73, 0x52, 0x37, 0xcc, 0xe8, 0x79, 0xb9, 0x81, 0xfa, 0xb4, 0xad, 0xe5, 0x7c, 0xf8, 0x78, 0x11, 0xb6, 0x36, 0x56, 0xfa, 0x11, 0x7c, 0x36, 0x47, 0x8d, 0x2d }, + { 0x33, 0x7a, 0xea, 0xfe, 0x2c, 0x17, 0xe3, 0xbc, 0xe8, 0x03, 0xcf, 0xe8, 0x52, 0xf9, 0xe5, 0x6a, 0x30, 0xaf, 0x55, 0xcf, 0x80, 0x47, 0xe2, 0x18, 0x6c, 0x01, 0x2c, 0xde, 0x8c, 0x44, 0x01, 0x9c }, + { 0x58, 0x5a, 0xfd, 0x28, 0x94, 0xac, 0xb6, 0x55, 0xcc, 0x03, 0xae, 0x8d, 0xe0, 0x59, 0x68, 0x63, 0xeb, 0x73, 0x37, 0x1d, 0xc7, 0x77, 0xdf, 0x80, 0xfd, 0x52, 0x08, 0x61, 0x19, 0x52, 0x05, 0xfe }, + { 0x2d, 0xba, 0xef, 0x44, 0xea, 0xe2, 0xa6, 0xc4, 0x8a, 0x0a, 0x4b, 0xaa, 0xa3, 0x40, 0x81, 0xf0, 0x0e, 0xce, 0xa5, 0xa5, 0x51, 0xbe, 0x52, 0x23, 0x10, 0xdd, 0x97, 0x16, 0x49, 0xc0, 0x4d, 0xac }, + { 0x61, 0x4a, 0x26, 0x94, 0x52, 0x40, 0xd9, 0xd9, 0xc5, 0xad, 0x07, 0x20, 0x5d, 0x60, 0x4f, 0xc3, 0x65, 0x73, 0xc7, 0xb6, 0x98, 0xa2, 0xe8, 0xeb, 0xc4, 0x7b, 0x63, 0x30, 0xa5, 0x8c, 0x99, 0xc9 }, + { 0x44, 0x5c, 0x06, 0x8d, 0x50, 0xc3, 0x93, 0xd0, 0xff, 0x07, 0x48, 0xb6, 0xbd, 0xbd, 0xf1, 0x85, 0x15, 0x40, 0x95, 0x36, 0xf8, 0x79, 0xed, 0x51, 0xdf, 0x83, 0xe7, 0xf9, 0x05, 0x54, 0x2f, 0x2d }, + { 0xdc, 0x18, 0x53, 0x72, 0x14, 0x38, 0xee, 0xa0, 0x56, 0x5d, 0x83, 0xf8, 0x40, 0x51, 0x96, 0xec, 0x63, 0xd1, 0xee, 0x6f, 0x36, 0x40, 0xea, 0x55, 0x1f, 0x8a, 0x85, 0xc4, 0xac, 0x0f, 0x06, 0x15 }, + { 0x2b, 0x09, 0xfe, 0xe2, 0x8c, 0xc6, 0xf8, 0xc7, 0xc3, 0x5e, 0xc0, 0x33, 0x64, 0xd3, 0xf1, 0xa9, 0xa1, 0x4b, 0x01, 0x19, 0xf7, 0x54, 0xd9, 0xe2, 0xd6, 0x54, 0x5d, 0xa9, 0xd3, 0xbb, 0x18, 0x4e }, + { 0x0a, 0x98, 0xb6, 0xea, 0x43, 0xc0, 0x27, 0x10, 0x35, 0x61, 0x08, 0xe3, 0x29, 0x9c, 0xde, 0x1a, 0xf5, 0xda, 0xda, 0xb2, 0xec, 0x81, 0x39, 0x8f, 0xef, 0xed, 0xee, 0x59, 0x01, 0x32, 0xa7, 0x00 }, + { 0xc6, 0x43, 0x26, 0x26, 0x80, 0x88, 0x08, 0x02, 0x50, 0xb7, 0x3f, 0x6c, 0xe2, 0x78, 0xba, 0x15, 0x24, 0x6d, 0x58, 0x1a, 0xd2, 0x31, 0x6a, 0x7a, 0x45, 0xb2, 0x7a, 0xf4, 0x52, 0x70, 0xdf, 0xbf }, + { 0x47, 0xf5, 0x74, 0xde, 0xb5, 0xcc, 0x24, 0x7b, 0xf4, 0xba, 0x4b, 0xe4, 0xef, 0x91, 0x28, 0x26, 0x03, 0xb0, 0xdf, 0x2d, 0x43, 0x62, 0x8d, 0x01, 0x6c, 0xc9, 0x50, 0xd5, 0xef, 0x0d, 0x99, 0x56 }, + { 0xbb, 0xa0, 0xd4, 0x38, 0x11, 0xf6, 0xa1, 0xd3, 0xee, 0xe7, 0x53, 0xd6, 0xd3, 0x77, 0xb4, 0xae, 0xef, 0x11, 0xba, 0xbc, 0x8b, 0x6d, 0xa2, 0xa5, 0xa1, 0x25, 0x96, 0x33, 0xb2, 0x0a, 0x6c, 0x06 }, + { 0xf1, 0x75, 0x0e, 0xe9, 0xc6, 0x9d, 0xf6, 0x2d, 0x8f, 0x3b, 0x2d, 0x21, 0xd5, 0x62, 0xac, 0x5b, 0x2f, 0x78, 0x73, 0x83, 0x15, 0x89, 0x74, 0xec, 0x69, 0x3c, 0xe5, 0xfe, 0xd6, 0x49, 0xfd, 0xab }, + { 0xa9, 0xcd, 0x78, 0x98, 0x04, 0xf9, 0xf2, 0xed, 0x9b, 0xd5, 0x21, 0xc7, 0x21, 0x06, 0x41, 0x6e, 0xa2, 0xbc, 0xa0, 0xdc, 0xf7, 0xbd, 0x00, 0x20, 0x3f, 0x3b, 0xe6, 0xf0, 0x5b, 0x65, 0xcb, 0xe0 }, + { 0x41, 0x0e, 0x99, 0x6a, 0x1c, 0x40, 0xed, 0x19, 0xa8, 0xed, 0x4c, 0x65, 0xbe, 0x33, 0x58, 0x50, 0x0b, 0x08, 0xb9, 0xac, 0xb9, 0xe1, 0x3f, 0xa7, 0xa6, 0x7e, 0x52, 0x04, 0xd0, 0xb3, 0x58, 0x6f }, + { 0xa7, 0x99, 0x5b, 0x2b, 0x0a, 0x68, 0x1b, 0x02, 0xee, 0x36, 0x6d, 0x08, 0x37, 0x8f, 0x22, 0x58, 0x8b, 0x4d, 0x90, 0x93, 0x4e, 0x18, 0xee, 0x45, 0x58, 0x36, 0xe4, 0x2c, 0xca, 0x7d, 0xc2, 0xe0 }, + { 0x98, 0x34, 0xf8, 0xee, 0xde, 0xf5, 0xa1, 0x83, 0x34, 0xa3, 0xc9, 0x1d, 0xcb, 0x21, 0x17, 0x45, 0x58, 0x5a, 0xfa, 0x10, 0x73, 0x21, 0x5e, 0x2d, 0xf5, 0x06, 0xee, 0x82, 0x6f, 0x13, 0x9c, 0x78 }, + { 0xa7, 0x1d, 0xff, 0x4f, 0xa1, 0x0e, 0xe9, 0x99, 0x6d, 0x88, 0x21, 0xbc, 0x12, 0x3e, 0x10, 0x93, 0x3e, 0x97, 0x11, 0xf7, 0x3e, 0x21, 0x73, 0x51, 0x0c, 0x78, 0xcd, 0x88, 0x44, 0x6b, 0x24, 0x63 }, + { 0xd8, 0xd9, 0x36, 0xd7, 0x1e, 0xa6, 0x99, 0x25, 0x24, 0x14, 0x85, 0xe5, 0x6d, 0x52, 0xd2, 0x6a, 0x3e, 0x76, 0xf7, 0xe3, 0xd1, 0x1c, 0x2d, 0xf4, 0xc9, 0x03, 0xb1, 0x5c, 0x0d, 0xdb, 0x46, 0xcb }, + { 0xda, 0x28, 0xb2, 0x45, 0xab, 0xae, 0x22, 0x98, 0x22, 0x23, 0x92, 0x7f, 0xe7, 0x3a, 0x5d, 0x51, 0xc3, 0x7a, 0x7a, 0x6e, 0x94, 0x13, 0xa1, 0x67, 0x90, 0x68, 0x6b, 0xa2, 0x12, 0xc7, 0x37, 0x3b }, + { 0x7a, 0x7b, 0x03, 0x07, 0xdb, 0x35, 0x0d, 0x0f, 0x5d, 0xfe, 0x95, 0x28, 0xab, 0x0c, 0x43, 0xad, 0x88, 0x43, 0xeb, 0xff, 0xda, 0x8d, 0x43, 0x8e, 0xfd, 0x22, 0x5e, 0xbe, 0x7e, 0x10, 0x89, 0xc5 }, + { 0x86, 0xda, 0xb7, 0xc4, 0x39, 0xd1, 0x51, 0x65, 0xe4, 0x0b, 0x31, 0xf9, 0x18, 0x1f, 0x10, 0x8b, 0xc1, 0xc4, 0xc1, 0xf2, 0x53, 0x31, 0x73, 0x96, 0x63, 0x11, 0x37, 0x30, 0x01, 0x37, 0xc8, 0x17 }, + { 0x9e, 0x39, 0x42, 0xd1, 0x02, 0xcb, 0x80, 0x2e, 0xa5, 0xa7, 0x0b, 0x9f, 0x7f, 0x70, 0x9e, 0xc5, 0x81, 0xfb, 0xf6, 0x1d, 0xec, 0x18, 0xd1, 0x7c, 0x8f, 0xc9, 0xcb, 0xc6, 0xd4, 0xf8, 0x59, 0xd5 }, + { 0xb3, 0x81, 0xdd, 0x38, 0xa9, 0x15, 0x30, 0x82, 0x1c, 0xfe, 0x5e, 0x66, 0x83, 0xcb, 0x08, 0x29, 0x8c, 0x59, 0x8d, 0x97, 0x81, 0x7a, 0xae, 0x9a, 0x71, 0xa5, 0x45, 0x7a, 0x76, 0x97, 0xe7, 0xf6 }, + { 0xf3, 0x12, 0xbd, 0x3c, 0x65, 0x81, 0xa9, 0xc2, 0x30, 0xe7, 0x98, 0x31, 0x83, 0x49, 0x49, 0xad, 0xdd, 0x3d, 0x26, 0x85, 0xfe, 0x15, 0x59, 0xff, 0xd4, 0x71, 0xed, 0x6a, 0x34, 0xbc, 0x49, 0x74 }, + { 0x30, 0x26, 0x30, 0x72, 0xf9, 0x9b, 0xde, 0x6f, 0x4c, 0xb0, 0xa2, 0x4e, 0x97, 0x1d, 0xf1, 0x20, 0x56, 0xd4, 0x7a, 0x27, 0x1b, 0xfa, 0x69, 0x2a, 0x86, 0x54, 0x3f, 0x6b, 0x7b, 0x2b, 0xe7, 0x5a }, + { 0x0e, 0x35, 0x3e, 0xef, 0x58, 0xa4, 0x05, 0x28, 0x5d, 0x72, 0xd3, 0xf2, 0x96, 0x22, 0xe1, 0x88, 0xb7, 0x4e, 0xdf, 0xcf, 0xae, 0x2d, 0xc4, 0x0c, 0x91, 0x6c, 0x37, 0xdc, 0xa5, 0xa2, 0x6b, 0xb8 }, + { 0xd4, 0xe0, 0xb0, 0xae, 0xb4, 0x65, 0xae, 0xfd, 0xd4, 0xe4, 0x23, 0x2a, 0xae, 0xb4, 0xf6, 0x65, 0x69, 0xbd, 0xcc, 0x22, 0xc9, 0xf7, 0x79, 0xd6, 0x61, 0xa7, 0x44, 0xef, 0xaf, 0x49, 0xef, 0xc1 }, + { 0xf3, 0x9d, 0x9b, 0xc0, 0xca, 0xe4, 0x54, 0x59, 0x78, 0xea, 0xbd, 0x03, 0x25, 0x2c, 0x7d, 0x3e, 0x5d, 0x89, 0xf3, 0x6e, 0x13, 0x46, 0x88, 0xf2, 0xab, 0xa7, 0x36, 0x13, 0x72, 0xc8, 0xc2, 0x26 }, + { 0xab, 0x7f, 0xf0, 0x26, 0xb0, 0xf1, 0x3c, 0x60, 0x74, 0x72, 0x4b, 0xc3, 0x7e, 0x38, 0x3f, 0x76, 0x4a, 0xdc, 0xa0, 0xd0, 0x87, 0x73, 0x60, 0x90, 0x82, 0x58, 0xa2, 0x7c, 0x44, 0xbf, 0x3b, 0x38 }, + { 0xf2, 0x3c, 0x94, 0x1a, 0x53, 0xd9, 0x32, 0xc4, 0xac, 0x35, 0x46, 0xa1, 0x17, 0x81, 0x0f, 0x48, 0xda, 0x3b, 0x88, 0x21, 0x22, 0x9d, 0xc8, 0xc0, 0x1b, 0x8b, 0xf5, 0x8b, 0xc3, 0x34, 0xdc, 0x37 }, + { 0x09, 0x7e, 0x34, 0xdc, 0xd4, 0xfc, 0xdd, 0xeb, 0x06, 0xd0, 0xc2, 0xd9, 0x35, 0xd6, 0x04, 0x02, 0xd1, 0x3d, 0x83, 0x24, 0x18, 0xa9, 0x65, 0x5e, 0xbe, 0xab, 0x19, 0xb6, 0x4d, 0x8a, 0x8f, 0xa5 }, + { 0xc1, 0x81, 0x43, 0xcc, 0xfa, 0x86, 0x60, 0xe3, 0xd8, 0x09, 0xbd, 0x26, 0x96, 0xb7, 0x8b, 0xa6, 0xe9, 0x2e, 0xa9, 0x0d, 0xb0, 0x54, 0xa4, 0xd7, 0x85, 0x97, 0xb8, 0xf5, 0x6f, 0x14, 0x53, 0xb6 }, + { 0xab, 0xda, 0xed, 0xb2, 0x47, 0x0e, 0xdc, 0xcd, 0xed, 0x3b, 0xee, 0x80, 0xf5, 0x32, 0xe4, 0x18, 0x1d, 0x2d, 0xfe, 0xea, 0x5a, 0x68, 0xc5, 0xf8, 0x60, 0x05, 0xa0, 0xde, 0xc2, 0xfd, 0x1b, 0xf6 }, + { 0x3e, 0x05, 0xac, 0x33, 0x0d, 0x42, 0xc0, 0xe9, 0xb1, 0xbc, 0xfd, 0x2a, 0xbc, 0xc5, 0xcb, 0x98, 0x52, 0x7e, 0x9c, 0xd1, 0x1c, 0xa8, 0x3d, 0x05, 0xa3, 0x46, 0x83, 0x88, 0xe0, 0xec, 0xbf, 0x0d }, + { 0x3c, 0x66, 0x18, 0x83, 0x29, 0xfa, 0xf5, 0x1e, 0xc4, 0x0c, 0x19, 0x8b, 0xeb, 0x20, 0x24, 0xbf, 0x57, 0x4b, 0xf0, 0x4b, 0xee, 0x20, 0x13, 0xf8, 0x68, 0xbc, 0x53, 0x4c, 0xb9, 0x6c, 0x44, 0x81 }, + { 0x63, 0x5b, 0x81, 0xab, 0x2a, 0x56, 0x27, 0x17, 0xf0, 0xb4, 0xb5, 0x70, 0xb7, 0x2d, 0xe3, 0x45, 0x78, 0xc1, 0x37, 0x63, 0xc1, 0x8c, 0x58, 0xc8, 0x6e, 0xfe, 0x09, 0xd4, 0x90, 0x15, 0x82, 0x40 }, + { 0x37, 0x06, 0xab, 0x4f, 0x7b, 0x65, 0x9c, 0xbe, 0xbf, 0x87, 0xe4, 0xa0, 0x21, 0x9f, 0xf6, 0x6b, 0x7a, 0x03, 0xa3, 0xda, 0x20, 0xd4, 0x6c, 0xba, 0xd6, 0xaa, 0xe7, 0x9e, 0x86, 0x38, 0xc3, 0xe4 }, + { 0xa8, 0x02, 0x74, 0x2c, 0x80, 0xb5, 0xa1, 0x82, 0x53, 0x3c, 0xc0, 0x1d, 0x0e, 0x30, 0x62, 0x08, 0x81, 0x5b, 0x80, 0xef, 0x5e, 0xc5, 0x76, 0x31, 0x2c, 0x1f, 0x2c, 0xa8, 0xc5, 0x0f, 0x0a, 0xd1 }, + { 0xc0, 0xc4, 0xdf, 0x2e, 0xe8, 0x39, 0xf3, 0x9a, 0xa3, 0x6f, 0xc1, 0xb1, 0x66, 0x33, 0x9f, 0x72, 0xdf, 0x4f, 0xdf, 0xf2, 0xec, 0x44, 0xf2, 0xd7, 0xd6, 0x32, 0x5b, 0xd8, 0xc7, 0x29, 0x72, 0x47 }, + { 0x2b, 0x46, 0x9c, 0xc7, 0x64, 0x54, 0x68, 0x6d, 0x34, 0xcd, 0xa8, 0xf6, 0xc3, 0x42, 0x6c, 0x68, 0x30, 0x6b, 0x0c, 0x86, 0xc7, 0xb8, 0xc2, 0x8c, 0x91, 0x89, 0xad, 0x23, 0x5b, 0xe1, 0x9d, 0x76 }, + { 0x89, 0xcf, 0xdc, 0x1e, 0x73, 0xd5, 0x1a, 0x36, 0xf8, 0x07, 0xff, 0xbc, 0x28, 0x4e, 0xff, 0x6a, 0xaf, 0xd8, 0xf2, 0xeb, 0xfe, 0x1b, 0x3a, 0x01, 0x44, 0x55, 0x4b, 0xc1, 0x49, 0x74, 0xa9, 0x1b }, + { 0x32, 0x3c, 0x62, 0xa3, 0x4c, 0xc0, 0xe2, 0x3a, 0xc1, 0xe5, 0x7b, 0x30, 0x59, 0xa6, 0xc4, 0x51, 0x4a, 0x3a, 0xff, 0x54, 0xb2, 0xcd, 0xed, 0x16, 0x40, 0x3d, 0xf2, 0x67, 0x21, 0xf0, 0x6e, 0x77 }, + { 0x49, 0x08, 0x3a, 0x0c, 0x40, 0x4e, 0x4e, 0x94, 0x5b, 0xad, 0x9b, 0xb8, 0x48, 0xcf, 0x3e, 0xa7, 0xec, 0xe1, 0x36, 0xed, 0xb6, 0x50, 0xa3, 0xbd, 0xbf, 0xca, 0x02, 0x9f, 0xe4, 0x11, 0x5b, 0xa0 }, + { 0x1a, 0x2a, 0x00, 0x9e, 0x9f, 0xcb, 0x39, 0xcc, 0xc8, 0x10, 0xcd, 0x9c, 0x8e, 0xb6, 0x05, 0xb1, 0x23, 0x47, 0x7b, 0x6a, 0x1e, 0xe2, 0xdc, 0x3e, 0xfe, 0x82, 0xd7, 0x2c, 0x18, 0xef, 0x2e, 0x58 }, + { 0x3e, 0x30, 0x80, 0x8c, 0xfe, 0xb7, 0xf7, 0x7d, 0x58, 0x09, 0x5c, 0x13, 0x07, 0x58, 0x08, 0x7e, 0xef, 0x11, 0x61, 0xb5, 0xa6, 0xea, 0x21, 0xdd, 0x27, 0xe7, 0xf4, 0x51, 0x38, 0x52, 0x89, 0xbe }, + { 0xb2, 0x77, 0x30, 0xbd, 0xdc, 0x39, 0xde, 0x78, 0x65, 0x45, 0xa6, 0xb2, 0xdb, 0x45, 0x13, 0x00, 0x6c, 0xff, 0x78, 0xa7, 0x2a, 0x27, 0x89, 0x4c, 0xff, 0xd3, 0x52, 0x2d, 0x71, 0x4e, 0xa7, 0x16 }, + { 0xec, 0x88, 0x16, 0x49, 0xcc, 0x51, 0xb8, 0x88, 0xb5, 0x2d, 0x2f, 0xaa, 0x15, 0x01, 0xb8, 0xaf, 0xfa, 0xa9, 0x57, 0x0d, 0xe0, 0x8e, 0x41, 0x28, 0x5b, 0x95, 0x7d, 0x9b, 0x94, 0x6a, 0xf6, 0x26 }, + { 0x84, 0x79, 0xb6, 0x99, 0x40, 0x61, 0xa7, 0x08, 0x70, 0x71, 0x6c, 0xf8, 0x8c, 0x65, 0xc7, 0xb6, 0x33, 0x50, 0xc7, 0x0f, 0x15, 0xbe, 0x6f, 0xa4, 0x61, 0x4d, 0x64, 0xa2, 0x15, 0x2e, 0xe3, 0xcd }, + { 0x2c, 0x06, 0x03, 0xa5, 0xd9, 0x74, 0x2e, 0x0d, 0x6b, 0x73, 0x55, 0x8c, 0x0f, 0x20, 0xa9, 0x06, 0xb1, 0x41, 0xd6, 0x0e, 0xe0, 0xcb, 0xb8, 0x09, 0x1a, 0x21, 0xff, 0x45, 0xde, 0x9f, 0x5f, 0x65 }, + { 0x5d, 0x37, 0xa4, 0x56, 0x3f, 0x0d, 0x86, 0xbe, 0x7a, 0x9d, 0x9f, 0x7c, 0xb2, 0x95, 0x8f, 0x43, 0xa0, 0x00, 0x06, 0x55, 0x78, 0x6d, 0x17, 0x18, 0xad, 0x74, 0x4b, 0xa8, 0xba, 0x3d, 0xaa, 0x25 }, + { 0xcc, 0x8a, 0xb6, 0x15, 0xea, 0x04, 0x65, 0x56, 0x54, 0xbb, 0x88, 0x78, 0x5a, 0xd2, 0x61, 0x8b, 0x19, 0x90, 0xc1, 0x8f, 0xdb, 0x27, 0xbe, 0xc0, 0x26, 0x12, 0xdb, 0xc2, 0xaf, 0x85, 0x93, 0x5e }, + { 0x7d, 0xb7, 0xd6, 0x0a, 0xf6, 0x27, 0x24, 0xbc, 0x74, 0xe4, 0x9f, 0xa6, 0x28, 0x8d, 0xce, 0x7f, 0x10, 0xdc, 0xe0, 0x81, 0xb2, 0x30, 0x9a, 0xba, 0x5e, 0x8a, 0xb1, 0x42, 0xd0, 0x84, 0xf6, 0xff }, + { 0x2d, 0x4f, 0x16, 0xcb, 0x08, 0xea, 0x01, 0xf3, 0x22, 0xae, 0x91, 0xa3, 0x74, 0xf4, 0x6d, 0x2b, 0x14, 0x9b, 0x99, 0x8b, 0x0d, 0xed, 0xc8, 0x1b, 0xba, 0x47, 0x6c, 0xb7, 0x31, 0x69, 0x39, 0x58 }, + { 0x51, 0xb2, 0x1a, 0xac, 0xae, 0x2b, 0xef, 0x19, 0x8e, 0xca, 0x3e, 0x06, 0x77, 0x2e, 0x24, 0xac, 0x07, 0x17, 0x80, 0x71, 0xe0, 0x86, 0x0e, 0xc9, 0x09, 0x33, 0xa9, 0xcb, 0xd0, 0xa4, 0x6e, 0xd9 }, + { 0xc1, 0x95, 0x38, 0x26, 0x50, 0x55, 0x0e, 0xf3, 0x6d, 0x53, 0x39, 0x2b, 0x11, 0x8f, 0xec, 0xfa, 0x79, 0x96, 0xc3, 0xad, 0x6b, 0x03, 0x71, 0xf2, 0x0a, 0x8f, 0x95, 0xcd, 0x91, 0x40, 0xa5, 0xd4 }, + { 0xfb, 0x2c, 0xd3, 0xfe, 0x85, 0xad, 0x72, 0x4b, 0xc3, 0xb2, 0x0c, 0x50, 0x50, 0xa9, 0x07, 0xc1, 0x22, 0xc9, 0x53, 0x18, 0xd5, 0xbc, 0x8d, 0x33, 0x27, 0xd4, 0x28, 0x0e, 0x21, 0xc5, 0xb2, 0x70 }, + { 0x6e, 0x6f, 0x49, 0x57, 0x8b, 0xc4, 0xf3, 0xe7, 0x55, 0x4a, 0xa9, 0x23, 0x42, 0x92, 0xd6, 0x08, 0xd8, 0xf7, 0x51, 0xc8, 0x8a, 0xc9, 0x65, 0x37, 0x4f, 0x2b, 0x23, 0xf2, 0x2c, 0x18, 0x98, 0xa7 }, + { 0x49, 0x07, 0x80, 0x52, 0xa5, 0x9f, 0xca, 0x9a, 0xf2, 0x24, 0x67, 0x0c, 0xac, 0xb9, 0x02, 0xf1, 0x0a, 0xe8, 0x0a, 0xe8, 0xc4, 0x9a, 0x89, 0x1b, 0x88, 0x3f, 0x73, 0xa8, 0x57, 0xd2, 0x89, 0x1d }, + { 0x46, 0xe0, 0x8f, 0xa8, 0xf2, 0x42, 0x9a, 0xd8, 0xb6, 0x9b, 0x2a, 0xc9, 0x06, 0x08, 0xc2, 0x6f, 0x9e, 0x45, 0x65, 0x8a, 0x86, 0x2d, 0x57, 0xfd, 0xa7, 0xd4, 0xd3, 0x5a, 0xa6, 0xb9, 0x72, 0x98 }, + { 0x93, 0x75, 0xb1, 0xff, 0xfd, 0x57, 0xa2, 0xcd, 0x6d, 0x3a, 0x33, 0xd4, 0xc7, 0x02, 0xb0, 0xba, 0xb5, 0xf3, 0xb6, 0x35, 0x0d, 0x95, 0x1b, 0x4f, 0x0c, 0xea, 0x40, 0x79, 0x45, 0x16, 0x3e, 0x48 }, + { 0x9e, 0xd4, 0x4c, 0xac, 0xea, 0x56, 0xab, 0xbe, 0xcb, 0x78, 0x76, 0xa7, 0xd8, 0x87, 0x66, 0xfc, 0x2e, 0xe0, 0x43, 0xb3, 0x69, 0xfb, 0x0a, 0xaf, 0x5d, 0x29, 0xd6, 0x7d, 0x0a, 0xea, 0x36, 0xa2 }, + { 0x94, 0x8a, 0xca, 0xa5, 0xa8, 0xa8, 0x44, 0x0d, 0x4d, 0x7a, 0x73, 0x97, 0xe7, 0x47, 0x84, 0xf3, 0x81, 0x87, 0xe1, 0xb2, 0x37, 0x00, 0xae, 0x29, 0xa6, 0x78, 0xc3, 0x1a, 0x60, 0x1d, 0x13, 0xc1 }, + { 0x01, 0x9a, 0x25, 0x5a, 0x28, 0x5d, 0xea, 0x68, 0x20, 0x1b, 0x20, 0xc0, 0x4e, 0xa3, 0xd4, 0x12, 0xf5, 0x33, 0x9c, 0xb9, 0x22, 0xfe, 0x5d, 0xfe, 0xbd, 0xc1, 0x87, 0x31, 0xdb, 0x8e, 0x9b, 0xa4 }, + { 0xb7, 0xcc, 0x1c, 0x62, 0xbc, 0x62, 0xcc, 0x3f, 0xb0, 0xaa, 0x7c, 0xba, 0x61, 0x1e, 0x03, 0xa2, 0x76, 0x8b, 0x6d, 0xe5, 0x0e, 0x80, 0x63, 0x85, 0x29, 0x23, 0xf8, 0x77, 0xda, 0xf9, 0x45, 0xd6 }, + { 0x87, 0x11, 0xc8, 0xac, 0x5a, 0xf3, 0xa8, 0xbb, 0x31, 0x33, 0x51, 0xa7, 0x68, 0x4c, 0x88, 0x78, 0xc1, 0xbc, 0xed, 0x94, 0xce, 0x12, 0xef, 0x81, 0x17, 0x02, 0xa6, 0xc7, 0x3a, 0x79, 0x12, 0xe4 }, + { 0x33, 0xca, 0xb9, 0x14, 0xdf, 0xe8, 0xae, 0xe0, 0xdc, 0xaa, 0xb2, 0x29, 0x29, 0xf2, 0x0c, 0x3d, 0xba, 0x22, 0x1c, 0x73, 0x21, 0xd4, 0x8e, 0x7f, 0x72, 0x36, 0xb6, 0xbf, 0x65, 0xc2, 0xf8, 0x34 }, + { 0xe5, 0xbc, 0xaf, 0x0d, 0xde, 0x3e, 0x00, 0x52, 0x2f, 0x61, 0x27, 0x77, 0x37, 0x3f, 0x08, 0xcd, 0x1c, 0x31, 0xa6, 0x5d, 0x50, 0x53, 0x02, 0x2f, 0x44, 0xc0, 0x94, 0xe9, 0x1b, 0xc6, 0xae, 0xdc }, + { 0xcf, 0x6b, 0x59, 0xce, 0x96, 0x5f, 0xdd, 0xf6, 0xb0, 0x29, 0xfa, 0xef, 0x65, 0x59, 0xc2, 0x93, 0xae, 0x7c, 0xe5, 0xec, 0xc7, 0x79, 0xb6, 0x87, 0xb9, 0xf3, 0x49, 0x3e, 0x64, 0x65, 0x58, 0xdd }, + { 0x6d, 0xf1, 0x3c, 0x6a, 0xe4, 0x34, 0x47, 0x13, 0x85, 0x16, 0x68, 0x1a, 0x50, 0xe5, 0x74, 0xbc, 0xe0, 0xa6, 0x8f, 0x3f, 0x14, 0x68, 0x5d, 0xf1, 0xe5, 0x04, 0x5b, 0x3a, 0xd1, 0x4b, 0x01, 0xd6 }, + { 0x97, 0x93, 0xea, 0x3d, 0x7d, 0x66, 0xc4, 0xf4, 0x37, 0x3b, 0xc1, 0x97, 0x09, 0xc9, 0xc5, 0x5d, 0xe5, 0x62, 0xde, 0x7e, 0x43, 0xa0, 0xb2, 0x33, 0x11, 0x7d, 0xc0, 0x6c, 0x55, 0xa6, 0xa4, 0x18 }, + { 0x02, 0x32, 0x50, 0x80, 0x99, 0x3c, 0xfa, 0x25, 0x37, 0x93, 0x11, 0xd5, 0x2e, 0x03, 0x1a, 0xb2, 0x7d, 0xc3, 0x06, 0xd7, 0x04, 0xc0, 0xd3, 0x3c, 0xcb, 0x6e, 0x32, 0x12, 0xcc, 0x08, 0x67, 0x4c }, + { 0xce, 0xa7, 0x08, 0xe1, 0xfd, 0x0a, 0x96, 0x03, 0x95, 0x42, 0xd9, 0x11, 0xc1, 0x6d, 0xf0, 0xc0, 0x00, 0x06, 0x2a, 0x20, 0xff, 0x42, 0x25, 0x60, 0x41, 0xfe, 0x35, 0xba, 0x08, 0x03, 0x99, 0x5e }, + { 0xa8, 0xf8, 0xbe, 0x4a, 0x4f, 0x87, 0x1c, 0x41, 0xc5, 0xb6, 0xc8, 0x29, 0x66, 0x99, 0x07, 0x07, 0x02, 0x7a, 0x09, 0xe8, 0x7e, 0x37, 0x28, 0x4c, 0x54, 0xe8, 0x2b, 0x58, 0x86, 0xc3, 0x3f, 0xb6 }, + { 0x47, 0xb6, 0x0e, 0x9e, 0x93, 0xeb, 0xf5, 0xae, 0xa2, 0x6d, 0x73, 0xd4, 0xba, 0xc8, 0x5a, 0xad, 0x39, 0xa5, 0xbe, 0x64, 0x54, 0x34, 0xd7, 0xeb, 0x90, 0x7a, 0xb4, 0xbe, 0xb9, 0x73, 0x88, 0x1b }, + { 0x8d, 0x7d, 0xed, 0x77, 0x15, 0xf4, 0x79, 0x20, 0x1b, 0xd8, 0x0c, 0x15, 0x21, 0x9f, 0xea, 0x6e, 0xb9, 0xae, 0xb2, 0x9d, 0x5d, 0x94, 0x20, 0xa3, 0x71, 0x8c, 0xf6, 0x60, 0x5c, 0x64, 0xc5, 0x6c }, + { 0xf2, 0x19, 0x2d, 0x9d, 0x51, 0x60, 0x22, 0x05, 0xcc, 0xde, 0x99, 0xfc, 0x5b, 0x18, 0x4d, 0x9d, 0x05, 0x19, 0x66, 0xd8, 0x6a, 0xbd, 0x61, 0x8f, 0xeb, 0x5c, 0x28, 0x8a, 0x59, 0xf4, 0xd2, 0x38 }, + { 0xa4, 0xbd, 0x01, 0x7a, 0x2b, 0xab, 0x8e, 0x78, 0x8c, 0x2d, 0xf1, 0x47, 0xe7, 0xf4, 0xf2, 0xee, 0x8f, 0xce, 0x8a, 0xe5, 0xfe, 0x4c, 0xe6, 0x16, 0x94, 0xcb, 0x8f, 0xf4, 0xe1, 0x00, 0x08, 0x15 }, + { 0xb2, 0x33, 0x51, 0x9b, 0x87, 0x23, 0x26, 0xf0, 0xfc, 0x9f, 0x9a, 0x30, 0x58, 0xf3, 0x7f, 0x61, 0xc5, 0x01, 0x1f, 0xf7, 0x3e, 0x18, 0x36, 0x48, 0xdf, 0xef, 0xef, 0xef, 0xbe, 0x26, 0x97, 0xdf }, + { 0xa2, 0x0a, 0xf0, 0x97, 0x37, 0x0c, 0x86, 0xfb, 0x28, 0x11, 0x5e, 0x9f, 0x18, 0x49, 0x1a, 0x84, 0xd7, 0x19, 0x14, 0x3a, 0x2f, 0xda, 0xe4, 0xf6, 0xa9, 0x59, 0x11, 0xa2, 0x26, 0x9a, 0x08, 0x12 }, + { 0x40, 0x34, 0x55, 0xd7, 0x4a, 0x4b, 0xe9, 0x90, 0xfb, 0xb5, 0x5d, 0x75, 0xc4, 0xa4, 0x04, 0x37, 0xa3, 0xd1, 0xf8, 0xa9, 0x05, 0xba, 0x89, 0xec, 0xfb, 0xc0, 0x32, 0xdf, 0x68, 0x4d, 0xda, 0xc5 }, + { 0x4b, 0xb6, 0xb4, 0xb0, 0x89, 0x13, 0xf2, 0x60, 0x88, 0x3b, 0xb8, 0xd0, 0x92, 0x8f, 0x25, 0x3e, 0x44, 0xc5, 0x20, 0xdc, 0xc1, 0xc8, 0x58, 0xe0, 0x08, 0x29, 0xca, 0xd0, 0xb3, 0x87, 0x9e, 0xe9 }, + { 0x57, 0xce, 0x4b, 0x96, 0xa1, 0x2b, 0xf1, 0xca, 0x26, 0x2c, 0x98, 0x33, 0x4f, 0x36, 0x70, 0xe1, 0x52, 0xa2, 0xfe, 0x28, 0xe7, 0xfd, 0xa8, 0x4f, 0xb7, 0x7c, 0xb3, 0x22, 0x9f, 0x19, 0x4b, 0xdd }, + { 0x70, 0xad, 0xc7, 0x0d, 0xd0, 0xa2, 0x9a, 0xee, 0x66, 0xb9, 0xe4, 0x03, 0x66, 0xd3, 0x10, 0x8e, 0x85, 0x20, 0x9c, 0x04, 0x30, 0xe8, 0x69, 0x74, 0x1e, 0x2b, 0x4d, 0xb3, 0x41, 0xf1, 0xdb, 0x96 }, + { 0x4f, 0x80, 0xee, 0x7d, 0x24, 0xe1, 0xfc, 0x73, 0x5a, 0x02, 0x85, 0x69, 0x07, 0x5b, 0x10, 0x8f, 0x82, 0x6c, 0x0c, 0xbc, 0x43, 0x93, 0x1f, 0x12, 0x96, 0xf7, 0x9c, 0x61, 0x70, 0x44, 0x56, 0xa6 }, + { 0x57, 0x18, 0x99, 0x32, 0x14, 0x1d, 0x23, 0xa2, 0x46, 0x0e, 0xdf, 0x52, 0xb7, 0xb7, 0x4d, 0x8f, 0x2f, 0x3c, 0x07, 0x77, 0x4d, 0xb1, 0xa3, 0x83, 0x46, 0x65, 0xed, 0xd0, 0xb9, 0x7a, 0x25, 0xba }, + { 0x7f, 0x02, 0xaa, 0x59, 0xc7, 0xbb, 0x9c, 0x68, 0xdd, 0x24, 0x3d, 0x00, 0x59, 0x40, 0x1c, 0x70, 0xb4, 0xbd, 0xd0, 0x33, 0x46, 0x4d, 0x0c, 0xcc, 0x46, 0x6a, 0xa1, 0x60, 0x8d, 0x54, 0x32, 0xc2 }, + { 0xad, 0x99, 0xed, 0x1c, 0xe9, 0x0b, 0xb4, 0xbb, 0x87, 0x18, 0x49, 0xee, 0x3d, 0x1f, 0x25, 0x34, 0x9c, 0x31, 0xb5, 0x4f, 0x19, 0x2b, 0xc1, 0xda, 0x65, 0x4d, 0x80, 0x50, 0xdd, 0x35, 0xf7, 0xc7 }, + { 0x80, 0x63, 0x59, 0xe2, 0x9b, 0x9c, 0x5a, 0xbe, 0xe0, 0xd0, 0xc1, 0x0b, 0xc2, 0x85, 0x5e, 0x2a, 0xd6, 0xed, 0xe3, 0x9a, 0x05, 0xe5, 0x47, 0xb0, 0x2a, 0xa0, 0x44, 0x80, 0x5a, 0xee, 0xb8, 0x09 }, + { 0x00, 0x14, 0xec, 0x7c, 0xc5, 0x71, 0x29, 0x3f, 0x45, 0xe8, 0x5f, 0x59, 0xf4, 0x8c, 0x85, 0x03, 0xbb, 0xe9, 0x20, 0xbc, 0x0d, 0x15, 0x4a, 0x2f, 0x0a, 0x99, 0x8f, 0xc3, 0x63, 0x0d, 0xb9, 0x4c }, + { 0xdd, 0xd7, 0xbe, 0x7d, 0xb8, 0xa3, 0xf1, 0x6f, 0x2a, 0xf8, 0x47, 0xf4, 0xc0, 0x32, 0xd4, 0x2e, 0xc1, 0xaa, 0xb5, 0x1e, 0xe8, 0x1c, 0xbb, 0x04, 0x5a, 0x0c, 0xf2, 0x16, 0xf8, 0xad, 0xae, 0xc9 }, + { 0x7b, 0xb9, 0x30, 0x2d, 0xd4, 0xf6, 0x25, 0x0f, 0x33, 0xd3, 0x86, 0x6f, 0xdc, 0x51, 0x32, 0x70, 0x83, 0x8e, 0x91, 0xd5, 0xfa, 0xfb, 0xab, 0x02, 0xb8, 0xb0, 0xfa, 0xc4, 0x1c, 0xdc, 0x89, 0x17 }, + { 0x1c, 0xa2, 0x9b, 0xe7, 0x4e, 0xd3, 0xcc, 0x44, 0xd0, 0x81, 0x68, 0x61, 0x24, 0x11, 0xa0, 0x9d, 0x9f, 0xb3, 0xf7, 0x13, 0x38, 0x3b, 0xef, 0x2e, 0xcc, 0x87, 0x79, 0x88, 0x73, 0x3d, 0xac, 0x25 }, + { 0xcc, 0x17, 0x23, 0x21, 0x4e, 0x75, 0xd7, 0xc1, 0xa5, 0x47, 0xad, 0x42, 0x77, 0x85, 0x03, 0x00, 0xa2, 0xfd, 0x03, 0x24, 0x5f, 0x4d, 0x14, 0x0e, 0xd7, 0x08, 0x21, 0xf4, 0xae, 0x43, 0x28, 0xaa }, + { 0x8a, 0xaf, 0xae, 0x77, 0x83, 0x12, 0xd3, 0xa5, 0x46, 0xad, 0x97, 0x23, 0x31, 0x7d, 0x21, 0x0d, 0x6c, 0x35, 0xcc, 0xa3, 0xa6, 0x56, 0x10, 0x1c, 0x5a, 0xc7, 0x41, 0x5a, 0xbc, 0x59, 0xad, 0x2b }, + { 0xa3, 0xd6, 0xf2, 0xdd, 0x71, 0x96, 0xa8, 0x3f, 0xf7, 0x69, 0x35, 0x23, 0x86, 0x0f, 0xee, 0xb5, 0x16, 0x3c, 0x35, 0x7e, 0xee, 0xad, 0xf6, 0x24, 0x6c, 0x21, 0xf3, 0x3e, 0x59, 0x8e, 0x4a, 0xaa }, + { 0x3f, 0xe6, 0xfc, 0xf8, 0x63, 0x88, 0xfb, 0x65, 0x0b, 0x28, 0x1f, 0xb7, 0xc0, 0x81, 0x61, 0x4d, 0x85, 0xa7, 0x89, 0xd7, 0x34, 0x44, 0xe5, 0x64, 0x22, 0x35, 0x89, 0x42, 0xdf, 0x5e, 0x1a, 0x6f }, + { 0xec, 0xd8, 0xab, 0xa9, 0x5f, 0xb1, 0xdc, 0xb5, 0x08, 0x2a, 0xb5, 0xa2, 0x22, 0x64, 0xc4, 0x54, 0x3e, 0x32, 0x79, 0x59, 0x78, 0xb9, 0xfb, 0x6b, 0x5c, 0x76, 0x66, 0x35, 0xa5, 0xa2, 0xbb, 0xba }, + { 0x2e, 0xab, 0x27, 0x1b, 0x58, 0xfe, 0xd4, 0x1b, 0xa9, 0x2f, 0x50, 0x38, 0x93, 0xb1, 0x00, 0xaf, 0x2c, 0x8f, 0xfd, 0xbe, 0x90, 0x18, 0x21, 0x41, 0xd6, 0x4f, 0x06, 0x52, 0xc6, 0xe7, 0xd5, 0x6b } +}}; +// clang-format on + +} // namespace bb::srs diff --git a/barretenberg/cpp/src/barretenberg/srs/factories/crs_factory.test.cpp b/barretenberg/cpp/src/barretenberg/srs/factories/crs_factory.test.cpp index b79ead7c4c13..fa9f74fcceaf 100644 --- a/barretenberg/cpp/src/barretenberg/srs/factories/crs_factory.test.cpp +++ b/barretenberg/cpp/src/barretenberg/srs/factories/crs_factory.test.cpp @@ -1,8 +1,10 @@ #include "barretenberg/api/file_io.hpp" #include "barretenberg/common/serialize.hpp" +#include "barretenberg/crypto/sha256/sha256.hpp" #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_g1_chunk_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" @@ -10,6 +12,7 @@ #include "barretenberg/srs/global_crs.hpp" #include #include +#include #include using namespace bb; @@ -122,3 +125,24 @@ TEST(CrsFactory, Bn254Fallback) fs::remove_all(temp_crs_path); } + +TEST(CrsFactory, Bn254ChunkHashFirstChunk) +{ + // Verify that the first 8MB chunk of the cached CRS matches the embedded hash + auto data = read_file(bb::srs::bb_crs_path() / "bn254_g1.dat", bb::srs::SRS_CHUNK_SIZE_BYTES); + auto chunk = std::span(data.data(), data.size()); + auto hash = bb::crypto::sha256(chunk); + EXPECT_EQ(hash, bb::srs::BN254_G1_CHUNK_HASHES[0]); +} + +TEST(CrsFactory, Bn254ChunkHashCorruptionDetected) +{ + // Verify that corrupted data fails chunk hash verification + auto data = read_file(bb::srs::bb_crs_path() / "bn254_g1.dat", bb::srs::SRS_CHUNK_SIZE_BYTES); + + // Corrupt a byte in the middle of the chunk + data[bb::srs::SRS_CHUNK_SIZE_BYTES / 2] ^= 0xFF; + auto chunk = std::span(data.data(), data.size()); + auto hash = bb::crypto::sha256(chunk); + EXPECT_NE(hash, bb::srs::BN254_G1_CHUNK_HASHES[0]); +} diff --git a/barretenberg/cpp/src/barretenberg/srs/factories/get_bn254_crs.cpp b/barretenberg/cpp/src/barretenberg/srs/factories/get_bn254_crs.cpp index 8e08f48deace..ce6c4bebfc95 100644 --- a/barretenberg/cpp/src/barretenberg/srs/factories/get_bn254_crs.cpp +++ b/barretenberg/cpp/src/barretenberg/srs/factories/get_bn254_crs.cpp @@ -2,10 +2,16 @@ #include "barretenberg/api/file_io.hpp" #include "barretenberg/common/flock.hpp" #include "barretenberg/common/serialize.hpp" +#include "barretenberg/common/thread.hpp" +#include "barretenberg/crypto/sha256/sha256.hpp" #include "barretenberg/ecc/curves/bn254/g1.hpp" #include "barretenberg/ecc/curves/bn254/g2.hpp" #include "bn254_crs_data.hpp" +#include "bn254_g1_chunk_hashes.hpp" #include "http_download.hpp" +#include +#include +#include namespace { // Primary CRS URL (Cloudflare R2) @@ -13,6 +19,77 @@ constexpr const char* CRS_PRIMARY_URL = "http://crs.aztec-cdn.foundation/g1.dat" // Fallback CRS URL (AWS S3) constexpr const char* CRS_FALLBACK_URL = "http://crs.aztec-labs.com/g1.dat"; +/** + * @brief Round num_points up to the next chunk boundary so every downloaded byte is hash-verified. + * Capped at SRS_TOTAL_POINTS (the full SRS size). + */ +size_t round_up_to_chunk_boundary(size_t num_points) +{ + if (num_points >= bb::srs::SRS_TOTAL_POINTS) { + return bb::srs::SRS_TOTAL_POINTS; + } + size_t rounded = ((num_points + bb::srs::SRS_CHUNK_SIZE_POINTS - 1) / bb::srs::SRS_CHUNK_SIZE_POINTS) * + bb::srs::SRS_CHUNK_SIZE_POINTS; + return std::min(rounded, bb::srs::SRS_TOTAL_POINTS); +} + +/** + * @brief Verify downloaded CRS data against embedded SHA-256 chunk hashes. + * + * @details Verifies all complete 8MB chunks in parallel across available cores with early-exit + * on first mismatch. Also verifies the partial last chunk (if present) so every downloaded byte + * is covered. Uses std::span to avoid per-chunk memory allocation. + */ +void verify_bn254_crs_integrity(const std::vector& data) +{ + size_t num_full_chunks = data.size() / bb::srs::SRS_CHUNK_SIZE_BYTES; + size_t chunks_to_verify = std::min(num_full_chunks, static_cast(bb::srs::SRS_NUM_FULL_CHUNKS)); + + // Sentinel value means "no failure found yet" + const size_t sentinel = bb::srs::SRS_NUM_CHUNKS; + std::atomic failed_chunk{ sentinel }; + + // Verify all complete 8MB chunks in parallel + if (chunks_to_verify > 0) { + bb::parallel_for([&](const bb::ThreadChunk& tc) { + for (size_t i : tc.range(chunks_to_verify)) { + // Early exit if another thread already found a mismatch + if (failed_chunk.load(std::memory_order_relaxed) < sentinel) { + return; + } + size_t offset = i * bb::srs::SRS_CHUNK_SIZE_BYTES; + auto chunk = std::span(data.data() + offset, bb::srs::SRS_CHUNK_SIZE_BYTES); + auto hash = bb::crypto::sha256(chunk); + if (hash != bb::srs::BN254_G1_CHUNK_HASHES[i]) { + size_t expected = sentinel; + failed_chunk.compare_exchange_strong(expected, i, std::memory_order_relaxed); + } + } + }); + } + + // Verify partial last chunk (e.g. the 64-byte tail of the full CRS) + size_t tail_offset = chunks_to_verify * bb::srs::SRS_CHUNK_SIZE_BYTES; + size_t tail_size = data.size() - tail_offset; + if (tail_size > 0 && chunks_to_verify < bb::srs::SRS_NUM_CHUNKS) { + auto tail = std::span(data.data() + tail_offset, tail_size); + auto hash = bb::crypto::sha256(tail); + if (hash != bb::srs::BN254_G1_CHUNK_HASHES[chunks_to_verify]) { + size_t expected = sentinel; + failed_chunk.compare_exchange_strong(expected, chunks_to_verify, std::memory_order_relaxed); + } + } + + size_t bad = failed_chunk.load(); + if (bad < sentinel) { + size_t offset = bad * bb::srs::SRS_CHUNK_SIZE_BYTES; + throw_or_abort("CRS integrity check failed: SHA-256 mismatch at chunk " + std::to_string(bad) + " (bytes " + + std::to_string(offset) + "+)"); + } + + vinfo("verified ", chunks_to_verify + (tail_size > 0 ? 1 : 0), " BN254 G1 CRS chunks via SHA-256"); +} + std::vector download_bn254_g1_data(size_t num_points, const std::string& primary_url, const std::string& fallback_url) @@ -40,19 +117,14 @@ std::vector download_bn254_g1_data(size_t num_points, throw_or_abort("Downloaded g1 data is too small"); } - // Verify first element matches our expected point. + // Quick sanity check: verify the first G1 point is the expected generator auto first_element = from_buffer(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(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."); - } - } + // Full integrity verification: SHA-256 chunk hashes in parallel + verify_bn254_crs_integrity(data); return data; } @@ -108,8 +180,10 @@ std::vector get_bn254_g1_data(const std::filesystem::path& p return points; } - vinfo("downloading bn254 crs..."); - auto data = download_bn254_g1_data(num_points, primary_url, fallback_url); + // Round up to chunk boundary so every downloaded byte is hash-verified + size_t download_points = round_up_to_chunk_boundary(num_points); + vinfo("downloading bn254 crs (", num_points, " points requested, downloading ", download_points, ")..."); + auto data = download_bn254_g1_data(download_points, primary_url, fallback_url); write_file(g1_path, data); auto points = std::vector(num_points); diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/field/field.test.cpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/field/field.test.cpp index da581bd33ed8..0c93fd4d2b99 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/field/field.test.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/field/field.test.cpp @@ -36,7 +36,7 @@ template class stdlib_field : public testing::Test { field_ct c = a + b; EXPECT_TRUE(field_ct::witness_indices_match(c, a)); EXPECT_TRUE(builder.get_num_finalized_gates_inefficient() == num_gates); - field_ct d(&builder, fr::coset_generator<0>()); // like b, d is just a constant and not a wire value + field_ct d(&builder, fr::coset_generator()); // like b, d is just a constant and not a wire value // by this point, we shouldn't have added any constraints in our circuit for (size_t i = 0; i < 17; ++i) { diff --git a/barretenberg/cpp/src/barretenberg/transcript/origin_tag.cpp b/barretenberg/cpp/src/barretenberg/transcript/origin_tag.cpp index 63286b89f8f6..e9d60e3a82fe 100644 --- a/barretenberg/cpp/src/barretenberg/transcript/origin_tag.cpp +++ b/barretenberg/cpp/src/barretenberg/transcript/origin_tag.cpp @@ -92,7 +92,8 @@ OriginTag::OriginTag(const OriginTag& tag_a, const OriginTag& tag_b) { // Elements with instant death should not be touched if (tag_a.instant_death || tag_b.instant_death) { - throw_or_abort("Touched an element that should not have been touched"); + throw_or_abort("Touched an element that should not have been touched. tag_a id: " + + std::to_string(tag_a.tag_id) + ", tag_b id: " + std::to_string(tag_b.tag_id)); } // If one of the tags is a constant, just use the other tag if (tag_a.transcript_index == CONSTANT) { @@ -107,7 +108,9 @@ OriginTag::OriginTag(const OriginTag& tag_a, const OriginTag& tag_b) // A free witness element should not interact with an element that has an origin if (tag_a.is_free_witness()) { if (!tag_b.is_free_witness() && !tag_b.is_empty()) { - throw_or_abort("A free witness element should not interact with an element that has an origin"); + throw_or_abort( + "A free witness element (id: " + std::to_string(tag_a.tag_id) + + ") should not interact with an element that has an origin (id: " + std::to_string(tag_b.tag_id) + ")"); } else { // If both are free witnesses or one of them is empty, just use tag_a *this = tag_a; @@ -116,7 +119,9 @@ OriginTag::OriginTag(const OriginTag& tag_a, const OriginTag& tag_b) } if (tag_b.is_free_witness()) { if (!tag_a.is_free_witness() && !tag_a.is_empty()) { - throw_or_abort("A free witness element should not interact with an element that has an origin"); + throw_or_abort( + "A free witness element (id: " + std::to_string(tag_b.tag_id) + + ") should not interact with an element that has an origin (id: " + std::to_string(tag_a.tag_id) + ")"); } else { // If both are free witnesses or one of them is empty, just use tag_b *this = tag_b; @@ -125,7 +130,10 @@ OriginTag::OriginTag(const OriginTag& tag_a, const OriginTag& tag_b) } // Elements from different transcripts shouldn't interact if (tag_a.transcript_index != tag_b.transcript_index) { - throw_or_abort("Tags from different transcripts were involved in the same computation"); + throw_or_abort("Tags from different transcripts were involved in the same computation. tag_a: { id: " + + std::to_string(tag_a.tag_id) + ", transcript: " + std::to_string(tag_a.transcript_index) + + " } tag_b: { id: " + std::to_string(tag_b.tag_id) + + ", transcript: " + std::to_string(tag_b.transcript_index) + " }"); } // Check that submitted values from different rounds don't mix without challenges check_round_provenance(tag_a.round_provenance, tag_b.round_provenance); diff --git a/barretenberg/cpp/src/barretenberg/transcript/origin_tag.hpp b/barretenberg/cpp/src/barretenberg/transcript/origin_tag.hpp index 614cb1e8c027..634dc8c3ce02 100644 --- a/barretenberg/cpp/src/barretenberg/transcript/origin_tag.hpp +++ b/barretenberg/cpp/src/barretenberg/transcript/origin_tag.hpp @@ -16,6 +16,7 @@ #include "barretenberg/common/assert.hpp" #include "barretenberg/common/throw_or_abort.hpp" #include "barretenberg/numeric/uint256/uint256.hpp" +#include #include #include #include @@ -68,6 +69,11 @@ void check_round_provenance(const uint256_t& provenance_a, const uint256_t& prov #ifndef AZTEC_NO_ORIGIN_TAGS struct OriginTag { + // Unique per-object ID for debugging: when a tag merge fails, the error message includes + // the IDs of both tags so you can trace which specific field elements were involved. + static inline std::atomic next_tag_id{ 0 }; + uint64_t tag_id = next_tag_id.fetch_add(1, std::memory_order_relaxed); + static constexpr size_t CONSTANT = static_cast(-1); static constexpr size_t FREE_WITNESS = static_cast(-2); // transcript_index represents the index of a unique transcript object that generated the value. It uses @@ -96,7 +102,7 @@ struct OriginTag { OriginTag& operator=(const OriginTag& other) = default; OriginTag& operator=(OriginTag&& other) noexcept { - + tag_id = other.tag_id; transcript_index = other.transcript_index; round_provenance = other.round_provenance; instant_death = other.instant_death; @@ -204,8 +210,8 @@ struct OriginTag { }; inline std::ostream& operator<<(std::ostream& os, OriginTag const& v) { - return os << "{ transcript_idx: " << v.transcript_index << ", round_prov: " << v.round_provenance - << ", instadeath: " << v.instant_death << " }"; + return os << "{ id: " << v.tag_id << ", transcript_idx: " << v.transcript_index + << ", round_prov: " << v.round_provenance << ", instadeath: " << v.instant_death << " }"; } #else diff --git a/barretenberg/cpp/src/barretenberg/transcript/origin_tag.test.cpp b/barretenberg/cpp/src/barretenberg/transcript/origin_tag.test.cpp index f8d78911fb54..9b2a5df7952c 100644 --- a/barretenberg/cpp/src/barretenberg/transcript/origin_tag.test.cpp +++ b/barretenberg/cpp/src/barretenberg/transcript/origin_tag.test.cpp @@ -242,4 +242,23 @@ TEST(OriginTag, RetaggingReflectsProtocolConstraints) EXPECT_NO_THROW(OriginTag(comm_times_challenge, eval_retagged)); } +// Test that unique per-object IDs are assigned +TEST(OriginTag, UniqueTagIds) +{ + auto tag_a = OriginTag(0, 0, true); + auto tag_b = OriginTag(0, 0, true); + + // Each constructed tag gets a unique ID + EXPECT_NE(tag_a.tag_id, tag_b.tag_id); + + // Copy preserves the ID + auto tag_a_copy = tag_a; + EXPECT_EQ(tag_a_copy.tag_id, tag_a.tag_id); + + // Merge produces a new ID + auto merged = OriginTag(tag_a, tag_b); + EXPECT_NE(merged.tag_id, tag_a.tag_id); + EXPECT_NE(merged.tag_id, tag_b.tag_id); +} + #endif // AZTEC_NO_ORIGIN_TAGS diff --git a/barretenberg/cpp/src/barretenberg/translator_vm/translator_verifier.cpp b/barretenberg/cpp/src/barretenberg/translator_vm/translator_verifier.cpp index 7aa36240939d..ab9ffba56527 100644 --- a/barretenberg/cpp/src/barretenberg/translator_vm/translator_verifier.cpp +++ b/barretenberg/cpp/src/barretenberg/translator_vm/translator_verifier.cpp @@ -6,6 +6,7 @@ #include "./translator_verifier.hpp" #include "barretenberg/commitment_schemes/shplonk/shplemini.hpp" +#include "barretenberg/common/bb_bench.hpp" #include "barretenberg/relations/translator_vm/translator_decomposition_relation_impl.hpp" #include "barretenberg/relations/translator_vm/translator_delta_range_constraint_relation_impl.hpp" #include "barretenberg/relations/translator_vm/translator_extra_relations_impl.hpp" @@ -133,6 +134,7 @@ template void TranslatorVerifier_::put_translation_dat template typename TranslatorVerifier_::ReductionResult TranslatorVerifier_::reduce_to_pairing_check() { + BB_BENCH_NAME("TranslatorVerifier::reduce"); using PCS = typename Flavor::PCS; using Shplemini = ShpleminiVerifier_; using ClaimBatcher = ClaimBatcher_; diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_verifier.cpp b/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_verifier.cpp index 0720061a8f4c..7ac039993752 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_verifier.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_verifier.cpp @@ -8,6 +8,7 @@ #include "barretenberg/commitment_schemes/ipa/ipa.hpp" #include "barretenberg/commitment_schemes/pairing_points.hpp" #include "barretenberg/commitment_schemes/shplonk/shplemini.hpp" +#include "barretenberg/common/bb_bench.hpp" #include "barretenberg/flavor/mega_avm_recursive_flavor.hpp" #include "barretenberg/flavor/mega_zk_recursive_flavor.hpp" #include "barretenberg/flavor/ultra_zk_recursive_flavor.hpp" @@ -231,6 +232,7 @@ template typename UltraVerifier_::Output UltraVerifier_::verify_proof( const typename UltraVerifier_::Proof& proof) { + BB_BENCH_NAME("UltraVerifier::verify_proof"); // Step 1: Split proof if needed Proof honk_proof; Proof ipa_proof; diff --git a/barretenberg/rust/barretenberg-rs/build.rs b/barretenberg/rust/barretenberg-rs/build.rs index a5880d55d677..3afeaee020d9 100644 --- a/barretenberg/rust/barretenberg-rs/build.rs +++ b/barretenberg/rust/barretenberg-rs/build.rs @@ -10,13 +10,9 @@ fn main() { // libbb-external.a contains everything needed: barretenberg + env + vm2_stub println!("cargo:rustc-link-lib=static=bb-external"); - // Link C++ standard library (different name on macOS/iOS vs Linux) - let target = std::env::var("TARGET").unwrap(); - if target.contains("apple") || target.contains("android") { - println!("cargo:rustc-link-lib=dylib=c++"); - } else { - println!("cargo:rustc-link-lib=dylib=stdc++"); - } + // Link C++ standard library + // barretenberg is built with Clang/libc++ on all platforms + println!("cargo:rustc-link-lib=dylib=c++"); } } diff --git a/noir-projects/aztec-nr/aztec/src/note/note_metadata.nr b/noir-projects/aztec-nr/aztec/src/note/note_metadata.nr index 8bd6c2bc36a7..cf5286556e54 100644 --- a/noir-projects/aztec-nr/aztec/src/note/note_metadata.nr +++ b/noir-projects/aztec-nr/aztec/src/note/note_metadata.nr @@ -82,7 +82,8 @@ impl NoteMetadata { self.stage == NoteStage.PENDING_PREVIOUS_PHASE } - /// Returns `true` if the note is settled, i.e. if it's been created in a prior transaction and is therefore already + /// Returns `true` if the note is settled, i.e. if it's been created in a prior transaction and is therefore + /// already /// in the note hash tree. pub fn is_settled(self) -> bool { self.stage == NoteStage.SETTLED diff --git a/yarn-project/p2p/src/client/p2p_client.test.ts b/yarn-project/p2p/src/client/p2p_client.test.ts index 20a41e28ec11..18707f11f101 100644 --- a/yarn-project/p2p/src/client/p2p_client.test.ts +++ b/yarn-project/p2p/src/client/p2p_client.test.ts @@ -381,12 +381,17 @@ describe('P2P Client', () => { it('triggers tx collection for missing txs from mined blocks', async () => { await client.start(); + // Drain any initial background sync that processes the initial blocks (1-100), + // which may call startCollecting depending on timing. + await client.sync(); + const block = await L2Block.random(BlockNumber(101), { txsPerBlock: 3 }); // Compute the block hash since it gets cached when the p2p client logs it await block.hash(); txPool.hasTxs.mockResolvedValue([true, false, true]); blockSource.addProposedBlocks([block]); + txCollection.startCollecting.mockClear(); await client.sync(); expect(txCollection.startCollecting).toHaveBeenCalledTimes(1);