diff --git a/.github/workflows/ci-master.yml b/.github/workflows/ci-master.yml index bed3ce726e..3ef2482be4 100644 --- a/.github/workflows/ci-master.yml +++ b/.github/workflows/ci-master.yml @@ -240,6 +240,7 @@ jobs: env PKG_CONFIG_PATH="$(realpath depends/$HOST_TRIPLET/lib/pkgconfig):$PKG_CONFIG_PATH" \ cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=$(realpath depends/$HOST_TRIPLET/toolchain.cmake) \ -DBUILD_CLI=ON -DBUILD_TESTS=ON -DENABLE_CRASH_HOOKS=ON -DBUILD_GUI=ON \ + -DBUILD_BENCH=ON -DBUILD_BENCH_SPARK=ON \ -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \ $CMAKE_EXTRA_FLAGS \ -S$(realpath .) -B$(realpath build) @@ -260,6 +261,7 @@ jobs: env PKG_CONFIG_PATH="$(realpath depends/$HOST_TRIPLET/lib/pkgconfig):$PKG_CONFIG_PATH" \ cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=$(realpath depends/$HOST_TRIPLET/toolchain.cmake) \ -DBUILD_CLI=ON -DBUILD_TESTS=OFF -DENABLE_CRASH_HOOKS=ON \ + -DBUILD_BENCH=ON -DBUILD_BENCH_SPARK=ON \ -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \ $CMAKE_EXTRA_FLAGS \ -DBUILD_GUI=ON -S$(realpath .) -B$(realpath build) @@ -280,6 +282,7 @@ jobs: env PKG_CONFIG_PATH="$(pwd)/depends/$HOST_TRIPLET/lib/pkgconfig:$PKG_CONFIG_PATH" \ cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=$(pwd)/depends/$HOST_TRIPLET/toolchain.cmake \ -DBUILD_CLI=ON -DBUILD_TESTS=OFF -DENABLE_CRASH_HOOKS=ON -DBUILD_GUI=ON \ + -DBUILD_BENCH=ON -DBUILD_BENCH_SPARK=ON \ -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \ $CMAKE_EXTRA_FLAGS \ -S$(pwd) -B$(pwd)/build @@ -297,6 +300,11 @@ jobs: set -exo pipefail cp -rf build/bin/* build/src/ qa/pull-tester/rpc-tests.py -extended + - name: Run Benchmarks + if: matrix.is_linux + run: | + set -exo pipefail + build/bin/bench_firo - name: Prepare Files for Artifact run: | set -exo pipefail diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f2b5dc911..aa66bfc776 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -283,6 +283,7 @@ if(BUILD_GUI) endif() option(BUILD_BENCH "Build bench_firo executable." OFF) +option(BUILD_BENCH_SPARK "Build Spark benchmarks into bench_firo." OFF) option(BUILD_FUZZ_BINARY "Build fuzz binary." OFF) option(BUILD_FOR_FUZZING "Build for fuzzing. Enabling this will disable all other targets and override BUILD_FUZZ_BINARY." OFF) @@ -339,6 +340,7 @@ if(BUILD_FOR_FUZZING) set(BUILD_TESTS OFF) set(BUILD_GUI_TESTS OFF) set(BUILD_BENCH OFF) + set(BUILD_BENCH_SPARK OFF) set(BUILD_FUZZ_BINARY ON) target_compile_definitions(core_interface INTERFACE @@ -853,7 +855,7 @@ if(DEFINED ENV{LDFLAGS}) deduplicate_flags(CMAKE_EXE_LINKER_FLAGS) endif() -if(BUILD_TESTS) +if(BUILD_TESTS OR BUILD_BENCH) enable_testing() endif() @@ -956,6 +958,7 @@ message("Tests:") message(" test_firo ........................ ${BUILD_TESTS}") message(" test_firo-qt ..................... ${BUILD_GUI_TESTS}") message(" bench_firo ....................... ${BUILD_BENCH}") +message(" Spark benchmarks ................. ${BUILD_BENCH_SPARK}") message(" fuzz binary ......................... ${BUILD_FUZZ_BINARY}") message("") if(CMAKE_CROSSCOMPILING) @@ -1006,4 +1009,4 @@ if(WIN32 AND MINGW AND STATIC_BUILD) # Ensure we link against static versions of system libraries set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-Bstatic") endif() -endif() \ No newline at end of file +endif() diff --git a/cmake/script/GenerateHeaderFromRaw.cmake b/cmake/script/GenerateHeaderFromRaw.cmake index 638876ecea..4e520de846 100644 --- a/cmake/script/GenerateHeaderFromRaw.cmake +++ b/cmake/script/GenerateHeaderFromRaw.cmake @@ -8,16 +8,20 @@ file(READ ${RAW_SOURCE_PATH} hex_content HEX) string(REGEX REPLACE "................" "\\0\n" formatted_bytes "${hex_content}") string(REGEX REPLACE "[^\n][^\n]" "std::byte{0x\\0}, " formatted_bytes "${formatted_bytes}") +string(LENGTH "${hex_content}" content_length) +math(EXPR array_size "${content_length} / 2") + +cmake_path(GET HEADER_PATH PARENT_PATH header_dir) +file(MAKE_DIRECTORY "${header_dir}") + set(header_content -"#include -#include +"#include +#include namespace ${RAW_NAMESPACE} { -inline constexpr std::byte detail_${raw_source_basename}_raw[] { +inline constexpr std::array ${raw_source_basename} {{ ${formatted_bytes} -}; - -inline constexpr std::span ${raw_source_basename}{detail_${raw_source_basename}_raw}; +}}; } ") file(WRITE ${HEADER_PATH} "${header_content}") diff --git a/cmake/utilities.cmake b/cmake/utilities.cmake index 4342b99401..1772a1287f 100644 --- a/cmake/utilities.cmake +++ b/cmake/utilities.cmake @@ -4,12 +4,17 @@ function(apply_wrapped_exception_flags target_name) if(ENABLE_CRASH_HOOKS AND CRASH_HOOKS_WRAPPED_CXX_ABI) - # We need to wrap exceptions to catch them in the crash handler - # We need to pass both compile flags and link flags to ensure that the wrapped exceptions are used in all cases - target_compile_options(${target_name} PRIVATE ${LDFLAGS_WRAP_EXCEPTIONS}) - # Apple linker does not support -Wl,--wrap= + get_target_property(target_type ${target_name} TYPE) + if(target_type STREQUAL "STATIC_LIBRARY") + set(wrap_scope INTERFACE) + else() + set(wrap_scope PRIVATE) + endif() + + # The wrappers are resolved by the final executable link step. Static + # libraries therefore propagate the requirement to their consumers. if(NOT APPLE) - target_link_options(${target_name} PRIVATE ${LDFLAGS_WRAP_EXCEPTIONS}) + target_link_options(${target_name} ${wrap_scope} ${LDFLAGS_WRAP_EXCEPTIONS}) endif() endif() endfunction() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index aa80161a0d..016b93f277 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -71,6 +71,7 @@ target_link_libraries(bitcoin_util bitcoin_crypto Boost::thread Boost::chrono + Boost::program_options univalue secp256k1 leveldb @@ -357,8 +358,8 @@ target_link_libraries(firo_node OpenSSL::SSL ${MINIUPNP_LIBRARY} ${ZLIB_LIBRARY} - $ $ + $ $ $ $<$:windows_system> @@ -533,6 +534,8 @@ endif() if(BUILD_BENCH) add_subdirectory(bench) +elseif(BUILD_BENCH_SPARK) + message(FATAL_ERROR "BUILD_BENCH_SPARK requires BUILD_BENCH=ON. Please add -DBUILD_BENCH=ON to your CMake command.") endif() if(BUILD_TESTS) @@ -570,4 +573,4 @@ if(INSTALL_MAN AND NOT WIN32) RENAME firo-tx.1 ) endif() -endif() \ No newline at end of file +endif() diff --git a/src/bench/CMakeLists.txt b/src/bench/CMakeLists.txt index 964aba54e0..609b8a16af 100644 --- a/src/bench/CMakeLists.txt +++ b/src/bench/CMakeLists.txt @@ -27,6 +27,8 @@ target_link_libraries(bench_firo core_interface firo_node Boost::headers + Boost::filesystem + secp256k1pp ) if(ENABLE_WALLET) @@ -44,3 +46,13 @@ add_test(NAME bench_sanity_check_high_priority install(TARGETS bench_firo RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) + +if(BUILD_BENCH_SPARK) + target_sources(bench_firo + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/spark_bpplus.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/spark_chaum.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/spark_grootle.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/spark_schnorr.cpp + ) +endif() diff --git a/src/bench/bench_bitcoin.cpp b/src/bench/bench_bitcoin.cpp index e236aac033..1b2c0575cd 100644 --- a/src/bench/bench_bitcoin.cpp +++ b/src/bench/bench_bitcoin.cpp @@ -4,11 +4,15 @@ #include "bench.h" +#include "chainparams.h" #include "key.h" +#include "pubkey.h" #include "stacktraces.h" #include "validation.h" #include "util.h" +#include + int main(int argc, char** argv) { @@ -17,10 +21,22 @@ main(int argc, char** argv) RegisterPrettyTerminateHander(); #endif ECC_Start(); - SetupEnvironment(); - fPrintToDebugLog = false; // don't want to write to debug.log file + { + ECCVerifyHandle globalVerifyHandle; + + SetupEnvironment(); + SelectParams(CBaseChainParams::MAIN); + fPrintToDebugLog = false; // don't want to write to debug.log file + + double elapsed_time_for_one = 1.0; + for (int i = 1; i < argc; ++i) { + if (std::string(argv[i]) == "-sanity-check") { + elapsed_time_for_one = 0.001; + } + } - benchmark::BenchRunner::RunAll(); + benchmark::BenchRunner::RunAll(elapsed_time_for_one); + } ECC_Stop(); } diff --git a/src/bench/checkblock.cpp b/src/bench/checkblock.cpp index 230e4ca773..a90535657f 100644 --- a/src/bench/checkblock.cpp +++ b/src/bench/checkblock.cpp @@ -9,9 +9,7 @@ #include "streams.h" #include "consensus/validation.h" -namespace block_bench { #include "bench/data/block413567.raw.h" -} // These are the two major time-sinks which happen after we have fully received // a block off the wire, but before we can relay the block on to peers using @@ -19,8 +17,8 @@ namespace block_bench { static void DeserializeBlockTest(benchmark::State& state) { - CDataStream stream((const char*)block_bench::block413567, - (const char*)&block_bench::block413567[sizeof(block_bench::block413567)], + CDataStream stream((const char*)benchmark::data::block413567.data(), + (const char*)benchmark::data::block413567.data() + benchmark::data::block413567.size(), SER_NETWORK, PROTOCOL_VERSION); char a; stream.write(&a, 1); // Prevent compaction @@ -28,14 +26,15 @@ static void DeserializeBlockTest(benchmark::State& state) while (state.KeepRunning()) { CBlock block; stream >> block; - assert(stream.Rewind(sizeof(block_bench::block413567))); + bool rewound = stream.Rewind(benchmark::data::block413567.size()); + assert(rewound); } } static void DeserializeAndCheckBlockTest(benchmark::State& state) { - CDataStream stream((const char*)block_bench::block413567, - (const char*)&block_bench::block413567[sizeof(block_bench::block413567)], + CDataStream stream((const char*)benchmark::data::block413567.data(), + (const char*)benchmark::data::block413567.data() + benchmark::data::block413567.size(), SER_NETWORK, PROTOCOL_VERSION); char a; stream.write(&a, 1); // Prevent compaction @@ -45,10 +44,13 @@ static void DeserializeAndCheckBlockTest(benchmark::State& state) while (state.KeepRunning()) { CBlock block; // Note that CBlock caches its checked state, so we need to recreate it here stream >> block; - assert(stream.Rewind(sizeof(block_bench::block413567))); + bool rewound = stream.Rewind(benchmark::data::block413567.size()); + assert(rewound); + // block413567.raw is a Bitcoin Core benchmark vector; keep the + // full-block structural checks, but do not require Firo PoW validity. CValidationState validationState; - assert(CheckBlock(block, validationState, params)); + assert(CheckBlock(block, validationState, params, false, true, 0)); } } diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp index 29fbd34631..34c0108c0b 100644 --- a/src/bench/coin_selection.cpp +++ b/src/bench/coin_selection.cpp @@ -3,11 +3,42 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "bench.h" + +#include "dbwrapper.h" +#include "llmq/quorums_instantsend.h" #include "wallet/wallet.h" #include #include +namespace { + +class ScopedInstantSendManager +{ +public: + ScopedInstantSendManager() + : db("", 1 << 20, true, true) + , manager(db) + , previous(llmq::quorumInstantSendManager) + { + if (previous == nullptr) { + llmq::quorumInstantSendManager = &manager; + } + } + + ~ScopedInstantSendManager() + { + if (previous == nullptr) { + llmq::quorumInstantSendManager = nullptr; + } + } + +private: + CDBWrapper db; + llmq::CInstantSendManager manager; + llmq::CInstantSendManager* const previous; +}; + static void addCoin(const CAmount& nValue, const CWallet& wallet, std::vector& vCoins) { int nInput = 0; @@ -33,6 +64,7 @@ static void addCoin(const CAmount& nValue, const CWallet& wallet, std::vector vCoins; LOCK(wallet.cs_wallet); @@ -57,4 +89,6 @@ static void CoinSelection(benchmark::State& state) } } +} // namespace + BENCHMARK(CoinSelection); diff --git a/src/bench/mempool_eviction.cpp b/src/bench/mempool_eviction.cpp index 5790d51a82..88877da85a 100644 --- a/src/bench/mempool_eviction.cpp +++ b/src/bench/mempool_eviction.cpp @@ -18,7 +18,7 @@ static void AddTx(const CTransaction& tx, const CAmount& nFee, CTxMemPool& pool) unsigned int sigOpCost = 4; LockPoints lp; pool.addUnchecked(tx.GetHash(), CTxMemPoolEntry( - MakeTransactionRef(tx), nFee, nTime, dPriority, nHeight, + MakeTransactionRef(tx), nFee, nTime, nHeight, tx.GetValueOut(), spendsCoinbase, sigOpCost, lp)); } diff --git a/src/bench/spark_bpplus.cpp b/src/bench/spark_bpplus.cpp new file mode 100644 index 0000000000..59d8a813e6 --- /dev/null +++ b/src/bench/spark_bpplus.cpp @@ -0,0 +1,215 @@ +// Copyright (c) 2026 The Firo Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "bench.h" + +#include "../libspark/bpplus.h" +#include "../libspark/params.h" + +#include +#include +#include +#include +#include + +namespace { + +using secp_primitives::GroupElement; +using secp_primitives::Scalar; + +std::size_t NextPowerOfTwo(const std::size_t n) +{ + if (spark::is_nonzero_power_of_2(n)) { + return n; + } + + return std::size_t{1} << (spark::log2(n) + 1); +} + +void RequireValid(const bool result) +{ + if (!result) { + std::abort(); + } +} + +struct BPPlusTestData { + std::size_t N; + std::size_t num_outputs; + + GroupElement G; + GroupElement H; + std::vector Gi; + std::vector Hi; + + std::vector v; + std::vector r; + std::vector C; + + BPPlusTestData(const std::size_t N_, const std::size_t num_outputs_) + : N(N_) + , num_outputs(num_outputs_) + { + const spark::Params* params = spark::Params::get_default(); + G = params->get_G(); + H = params->get_H(); + + const std::size_t padded_outputs = NextPowerOfTwo(num_outputs); + const std::size_t required_generators = N * padded_outputs; + const std::vector& default_Gi = params->get_G_range(); + const std::vector& default_Hi = params->get_H_range(); + if (required_generators > default_Gi.size() || required_generators > default_Hi.size()) { + throw std::invalid_argument("BPPlus benchmark exceeds default Spark range generators"); + } + Gi.assign(default_Gi.begin(), default_Gi.begin() + required_generators); + Hi.assign(default_Hi.begin(), default_Hi.begin() + required_generators); + + v.resize(num_outputs); + r.resize(num_outputs); + C.resize(num_outputs); + for (std::size_t i = 0; i < num_outputs; ++i) { + v[i] = Scalar(static_cast(i + 1)); + r[i].randomize(); + C[i] = G * v[i] + H * r[i]; + } + } +}; + +struct BPPlusBatchData { + BPPlusTestData params; + std::vector proofs; + std::vector> commitments; + + BPPlusBatchData(const std::size_t num_proofs, const std::size_t outputs_per_proof) + : params(64, outputs_per_proof) + { + spark::BPPlus bpplus(params.G, params.H, params.Gi, params.Hi, params.N); + + proofs.resize(num_proofs); + commitments.reserve(num_proofs); + for (std::size_t i = 0; i < num_proofs; ++i) { + std::vector v(outputs_per_proof); + std::vector r(outputs_per_proof); + std::vector C(outputs_per_proof); + + for (std::size_t j = 0; j < outputs_per_proof; ++j) { + v[j] = Scalar(static_cast((i + 1) * (j + 1))); + r[j].randomize(); + C[j] = params.G * v[j] + params.H * r[j]; + } + + bpplus.prove(v, r, C, proofs[i]); + commitments.emplace_back(C); + } + } +}; + +void BPPlusProve(benchmark::State& state, const std::size_t bit_length, const std::size_t outputs) +{ + BPPlusTestData data(bit_length, outputs); + spark::BPPlus bpplus(data.G, data.H, data.Gi, data.Hi, data.N); + + while (state.KeepRunning()) { + spark::BPPlusProof proof; + bpplus.prove(data.v, data.r, data.C, proof); + } +} + +void BPPlusVerify(benchmark::State& state, const std::size_t bit_length, const std::size_t outputs) +{ + BPPlusTestData data(bit_length, outputs); + spark::BPPlus bpplus(data.G, data.H, data.Gi, data.Hi, data.N); + + spark::BPPlusProof proof; + bpplus.prove(data.v, data.r, data.C, proof); + + while (state.KeepRunning()) { + RequireValid(bpplus.verify(data.C, proof)); + } +} + +void BPPlusBatchVerify(benchmark::State& state, const std::size_t num_proofs) +{ + BPPlusBatchData data(num_proofs, 2); + spark::BPPlus bpplus(data.params.G, data.params.H, data.params.Gi, data.params.Hi, data.params.N); + + while (state.KeepRunning()) { + RequireValid(bpplus.verify(data.commitments, data.proofs)); + } +} + +void SparkBPPlusProve1Output64Bit(benchmark::State& state) +{ + BPPlusProve(state, 64, 1); +} + +void SparkBPPlusProve2Outputs64Bit(benchmark::State& state) +{ + BPPlusProve(state, 64, 2); +} + +void SparkBPPlusProve4Outputs64Bit(benchmark::State& state) +{ + BPPlusProve(state, 64, 4); +} + +void SparkBPPlusProve8Outputs64Bit(benchmark::State& state) +{ + BPPlusProve(state, 64, 8); +} + +void SparkBPPlusProve2Outputs32Bit(benchmark::State& state) +{ + BPPlusProve(state, 32, 2); +} + +void SparkBPPlusProve2Outputs128Bit(benchmark::State& state) +{ + BPPlusProve(state, 128, 2); +} + +void SparkBPPlusVerify1Output64Bit(benchmark::State& state) +{ + BPPlusVerify(state, 64, 1); +} + +void SparkBPPlusVerify2Outputs64Bit(benchmark::State& state) +{ + BPPlusVerify(state, 64, 2); +} + +void SparkBPPlusVerify4Outputs64Bit(benchmark::State& state) +{ + BPPlusVerify(state, 64, 4); +} + +void SparkBPPlusVerify8Outputs64Bit(benchmark::State& state) +{ + BPPlusVerify(state, 64, 8); +} + +void SparkBPPlusBatchVerify10Proofs(benchmark::State& state) +{ + BPPlusBatchVerify(state, 10); +} + +void SparkBPPlusBatchVerify50Proofs(benchmark::State& state) +{ + BPPlusBatchVerify(state, 50); +} + +} // namespace + +BENCHMARK(SparkBPPlusProve1Output64Bit); +BENCHMARK(SparkBPPlusProve2Outputs64Bit); +BENCHMARK(SparkBPPlusProve4Outputs64Bit); +BENCHMARK(SparkBPPlusProve8Outputs64Bit); +BENCHMARK(SparkBPPlusProve2Outputs32Bit); +BENCHMARK(SparkBPPlusProve2Outputs128Bit); +BENCHMARK(SparkBPPlusVerify1Output64Bit); +BENCHMARK(SparkBPPlusVerify2Outputs64Bit); +BENCHMARK(SparkBPPlusVerify4Outputs64Bit); +BENCHMARK(SparkBPPlusVerify8Outputs64Bit); +BENCHMARK(SparkBPPlusBatchVerify10Proofs); +BENCHMARK(SparkBPPlusBatchVerify50Proofs); diff --git a/src/bench/spark_chaum.cpp b/src/bench/spark_chaum.cpp new file mode 100644 index 0000000000..a8c13db1bc --- /dev/null +++ b/src/bench/spark_chaum.cpp @@ -0,0 +1,141 @@ +// Copyright (c) 2026 The Firo Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "bench.h" + +#include "../libspark/chaum.h" +#include "../libspark/params.h" + +#include +#include +#include + +namespace { + +using secp_primitives::GroupElement; +using secp_primitives::Scalar; + +void RequireValid(const bool result) +{ + if (!result) { + std::abort(); + } +} + +struct ChaumTestData { + std::size_t n; + + GroupElement F; + GroupElement G; + GroupElement H; + GroupElement U; + Scalar mu; + + std::vector x; + std::vector y; + std::vector z; + std::vector S; + std::vector T; + + explicit ChaumTestData(const std::size_t n_) + : n(n_) + { + const spark::Params* params = spark::Params::get_default(); + F = params->get_F(); + G = params->get_G(); + H = params->get_H(); + U = params->get_U(); + mu.randomize(); + + x.resize(n); + y.resize(n); + z.resize(n); + S.resize(n); + T.resize(n); + + for (std::size_t i = 0; i < n; ++i) { + x[i].randomize(); + y[i].randomize(); + z[i].randomize(); + + S[i] = F * x[i] + G * y[i] + H * z[i]; + T[i] = (U + G * y[i].negate()) * x[i].inverse(); + } + } +}; + +void ChaumProve(benchmark::State& state, const std::size_t commitments) +{ + ChaumTestData data(commitments); + spark::Chaum chaum(data.F, data.G, data.H, data.U); + + while (state.KeepRunning()) { + spark::ChaumProof proof; + chaum.prove(data.mu, data.x, data.y, data.z, data.S, data.T, proof); + } +} + +void ChaumVerify(benchmark::State& state, const std::size_t commitments) +{ + ChaumTestData data(commitments); + spark::Chaum chaum(data.F, data.G, data.H, data.U); + + spark::ChaumProof proof; + chaum.prove(data.mu, data.x, data.y, data.z, data.S, data.T, proof); + + while (state.KeepRunning()) { + RequireValid(chaum.verify(data.mu, data.S, data.T, proof)); + } +} + +void SparkChaumProve1Commitment(benchmark::State& state) +{ + ChaumProve(state, 1); +} + +void SparkChaumProve2Commitments(benchmark::State& state) +{ + ChaumProve(state, 2); +} + +void SparkChaumProve4Commitments(benchmark::State& state) +{ + ChaumProve(state, 4); +} + +void SparkChaumProve8Commitments(benchmark::State& state) +{ + ChaumProve(state, 8); +} + +void SparkChaumVerify1Commitment(benchmark::State& state) +{ + ChaumVerify(state, 1); +} + +void SparkChaumVerify2Commitments(benchmark::State& state) +{ + ChaumVerify(state, 2); +} + +void SparkChaumVerify4Commitments(benchmark::State& state) +{ + ChaumVerify(state, 4); +} + +void SparkChaumVerify8Commitments(benchmark::State& state) +{ + ChaumVerify(state, 8); +} + +} // namespace + +BENCHMARK(SparkChaumProve1Commitment); +BENCHMARK(SparkChaumProve2Commitments); +BENCHMARK(SparkChaumProve4Commitments); +BENCHMARK(SparkChaumProve8Commitments); +BENCHMARK(SparkChaumVerify1Commitment); +BENCHMARK(SparkChaumVerify2Commitments); +BENCHMARK(SparkChaumVerify4Commitments); +BENCHMARK(SparkChaumVerify8Commitments); diff --git a/src/bench/spark_grootle.cpp b/src/bench/spark_grootle.cpp new file mode 100644 index 0000000000..749b9e58cf --- /dev/null +++ b/src/bench/spark_grootle.cpp @@ -0,0 +1,224 @@ +// Copyright (c) 2026 The Firo Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "bench.h" + +#include "../libspark/grootle.h" +#include "../random.h" + +#include +#include +#include + +namespace { + +using secp_primitives::GroupElement; +using secp_primitives::Scalar; + +void RequireValid(const bool result) +{ + if (!result) { + std::abort(); + } +} + +std::size_t Pow(const std::size_t base, const std::size_t exponent) +{ + std::size_t result = 1; + for (std::size_t i = 0; i < exponent; ++i) { + result *= base; + } + return result; +} + +struct GrootleTestData { + std::size_t n; + std::size_t m; + std::size_t set_size; + std::size_t l; + + GroupElement H; + std::vector Gi; + std::vector Hi; + + Scalar s; + std::vector S; + GroupElement S1; + + Scalar v; + std::vector V; + GroupElement V1; + + std::vector root; + + GrootleTestData(const std::size_t n_, const std::size_t m_) + : n(n_) + , m(m_) + , set_size(Pow(n_, m_)) + , l(set_size / 2) + , root(32) + { + H.randomize(); + + Gi.resize(n * m); + Hi.resize(n * m); + for (std::size_t i = 0; i < n * m; ++i) { + Gi[i].randomize(); + Hi[i].randomize(); + } + + s.randomize(); + v.randomize(); + + S.resize(set_size); + V.resize(set_size); + for (std::size_t i = 0; i < set_size; ++i) { + S[i].randomize(); + V[i].randomize(); + } + + S1 = S[l] + H * s.negate(); + V1 = V[l] + H * v.negate(); + + GetRandBytes(root.data(), static_cast(root.size())); + } +}; + +struct GrootleBatchData { + GrootleTestData data; + std::vector proofs; + std::vector S1; + std::vector V1; + std::vector> roots; + std::vector sizes; + + explicit GrootleBatchData(const std::size_t num_proofs) + : data(4, 3) + { + spark::Grootle grootle(data.H, data.Gi, data.Hi, data.n, data.m); + + proofs.resize(num_proofs); + S1.reserve(num_proofs); + V1.reserve(num_proofs); + roots.reserve(num_proofs); + sizes.reserve(num_proofs); + + for (std::size_t i = 0; i < num_proofs; ++i) { + const std::size_t l = i % data.set_size; + Scalar s; + Scalar v; + s.randomize(); + v.randomize(); + + GroupElement S1_i = data.S[l] + data.H * s.negate(); + GroupElement V1_i = data.V[l] + data.H * v.negate(); + + grootle.prove(l, s, data.S, S1_i, v, data.V, V1_i, data.root, proofs[i]); + + S1.emplace_back(S1_i); + V1.emplace_back(V1_i); + roots.emplace_back(data.root); + sizes.emplace_back(data.set_size); + } + } +}; + +void GrootleProve(benchmark::State& state, const std::size_t n, const std::size_t m) +{ + GrootleTestData data(n, m); + spark::Grootle grootle(data.H, data.Gi, data.Hi, data.n, data.m); + + while (state.KeepRunning()) { + spark::GrootleProof proof; + grootle.prove(data.l, data.s, data.S, data.S1, data.v, data.V, data.V1, data.root, proof); + } +} + +void GrootleVerify(benchmark::State& state, const std::size_t n, const std::size_t m) +{ + GrootleTestData data(n, m); + spark::Grootle grootle(data.H, data.Gi, data.Hi, data.n, data.m); + + spark::GrootleProof proof; + grootle.prove(data.l, data.s, data.S, data.S1, data.v, data.V, data.V1, data.root, proof); + + while (state.KeepRunning()) { + RequireValid(grootle.verify(data.S, data.S1, data.V, data.V1, data.root, data.set_size, proof)); + } +} + +void SparkGrootleProveN4M2(benchmark::State& state) +{ + GrootleProve(state, 4, 2); +} + +void SparkGrootleProveN4M3(benchmark::State& state) +{ + GrootleProve(state, 4, 3); +} + +void SparkGrootleProveN4M4(benchmark::State& state) +{ + GrootleProve(state, 4, 4); +} + +void SparkGrootleProveN8M3(benchmark::State& state) +{ + GrootleProve(state, 8, 3); +} + +void SparkGrootleProveN16M3(benchmark::State& state) +{ + GrootleProve(state, 16, 3); +} + +void SparkGrootleVerifyN4M2(benchmark::State& state) +{ + GrootleVerify(state, 4, 2); +} + +void SparkGrootleVerifyN4M3(benchmark::State& state) +{ + GrootleVerify(state, 4, 3); +} + +void SparkGrootleVerifyN4M4(benchmark::State& state) +{ + GrootleVerify(state, 4, 4); +} + +void SparkGrootleVerifyN16M3(benchmark::State& state) +{ + GrootleVerify(state, 16, 3); +} + +void SparkGrootleBatchVerify10Proofs(benchmark::State& state) +{ + GrootleBatchData batch(10); + spark::Grootle grootle(batch.data.H, batch.data.Gi, batch.data.Hi, batch.data.n, batch.data.m); + + while (state.KeepRunning()) { + RequireValid(grootle.verify( + batch.data.S, + batch.S1, + batch.data.V, + batch.V1, + batch.roots, + batch.sizes, + batch.proofs)); + } +} + +} // namespace + +BENCHMARK(SparkGrootleProveN4M2); +BENCHMARK(SparkGrootleProveN4M3); +BENCHMARK(SparkGrootleProveN4M4); +BENCHMARK(SparkGrootleProveN8M3); +BENCHMARK(SparkGrootleProveN16M3); +BENCHMARK(SparkGrootleVerifyN4M2); +BENCHMARK(SparkGrootleVerifyN4M3); +BENCHMARK(SparkGrootleVerifyN4M4); +BENCHMARK(SparkGrootleVerifyN16M3); +BENCHMARK(SparkGrootleBatchVerify10Proofs); diff --git a/src/bench/spark_schnorr.cpp b/src/bench/spark_schnorr.cpp new file mode 100644 index 0000000000..e925bc77a3 --- /dev/null +++ b/src/bench/spark_schnorr.cpp @@ -0,0 +1,131 @@ +// Copyright (c) 2026 The Firo Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "bench.h" + +#include "../libspark/params.h" +#include "../libspark/schnorr.h" + +#include +#include +#include + +namespace { + +using secp_primitives::GroupElement; +using secp_primitives::Scalar; + +struct SchnorrTestData { + std::size_t n; // number of keys + + GroupElement G; + std::vector y; + std::vector Y; + + explicit SchnorrTestData(const std::size_t n_) + : n(n_) + { + G = spark::Params::get_default()->get_H(); + + y.resize(n); + Y.resize(n); + + for (std::size_t i = 0; i < n; ++i) { + y[i].randomize(); + Y[i] = G * y[i]; + } + } +}; + +void RequireValid(const bool result) +{ + if (!result) { + std::abort(); + } +} + +void SchnorrProveKeys(benchmark::State& state, const std::size_t keys) +{ + SchnorrTestData data(keys); + spark::Schnorr schnorr(data.G); + + while (state.KeepRunning()) { + spark::SchnorrProof proof; + if (keys == 1) { + schnorr.prove(data.y[0], data.Y[0], proof); + } else { + schnorr.prove(data.y, data.Y, proof); + } + } +} + +void SchnorrVerifyKeys(benchmark::State& state, const std::size_t keys) +{ + SchnorrTestData data(keys); + spark::Schnorr schnorr(data.G); + spark::SchnorrProof proof; + + if (keys == 1) { + schnorr.prove(data.y[0], data.Y[0], proof); + while (state.KeepRunning()) { + RequireValid(schnorr.verify(data.Y[0], proof)); + } + } else { + schnorr.prove(data.y, data.Y, proof); + while (state.KeepRunning()) { + RequireValid(schnorr.verify(data.Y, proof)); + } + } +} + +void SparkSchnorrProve1Key(benchmark::State& state) +{ + SchnorrProveKeys(state, 1); +} + +void SparkSchnorrProve2Keys(benchmark::State& state) +{ + SchnorrProveKeys(state, 2); +} + +void SparkSchnorrProve4Keys(benchmark::State& state) +{ + SchnorrProveKeys(state, 4); +} + +void SparkSchnorrProve8Keys(benchmark::State& state) +{ + SchnorrProveKeys(state, 8); +} + +void SparkSchnorrVerify1Key(benchmark::State& state) +{ + SchnorrVerifyKeys(state, 1); +} + +void SparkSchnorrVerify2Keys(benchmark::State& state) +{ + SchnorrVerifyKeys(state, 2); +} + +void SparkSchnorrVerify4Keys(benchmark::State& state) +{ + SchnorrVerifyKeys(state, 4); +} + +void SparkSchnorrVerify8Keys(benchmark::State& state) +{ + SchnorrVerifyKeys(state, 8); +} + +} // namespace + +BENCHMARK(SparkSchnorrProve1Key); +BENCHMARK(SparkSchnorrProve2Keys); +BENCHMARK(SparkSchnorrProve4Keys); +BENCHMARK(SparkSchnorrProve8Keys); +BENCHMARK(SparkSchnorrVerify1Key); +BENCHMARK(SparkSchnorrVerify2Keys); +BENCHMARK(SparkSchnorrVerify4Keys); +BENCHMARK(SparkSchnorrVerify8Keys);