From 58fc80b787f350f400529a037579e9e412adcc3d Mon Sep 17 00:00:00 2001 From: Navid Rahimi Date: Sun, 19 Apr 2026 20:11:32 +0330 Subject: [PATCH 1/3] Fix bench_firo macOS C++20 compilation by replacing std::span with std::array and adding missing link dependencies --- cmake/script/GenerateHeaderFromRaw.cmake | 13 +++++++------ src/bench/CMakeLists.txt | 2 ++ src/bench/checkblock.cpp | 14 ++++++-------- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/cmake/script/GenerateHeaderFromRaw.cmake b/cmake/script/GenerateHeaderFromRaw.cmake index 638876ecea..4d21c92a92 100644 --- a/cmake/script/GenerateHeaderFromRaw.cmake +++ b/cmake/script/GenerateHeaderFromRaw.cmake @@ -8,16 +8,17 @@ 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") + 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/src/bench/CMakeLists.txt b/src/bench/CMakeLists.txt index 964aba54e0..be3f593cb1 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) diff --git a/src/bench/checkblock.cpp b/src/bench/checkblock.cpp index 230e4ca773..8ec965c376 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,14 @@ static void DeserializeBlockTest(benchmark::State& state) while (state.KeepRunning()) { CBlock block; stream >> block; - assert(stream.Rewind(sizeof(block_bench::block413567))); + assert(stream.Rewind(benchmark::data::block413567.size())); } } 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,7 +43,7 @@ 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))); + assert(stream.Rewind(benchmark::data::block413567.size())); CValidationState validationState; assert(CheckBlock(block, validationState, params)); From 3997f072d17940dbfa9e677f55060a83501e0145 Mon Sep 17 00:00:00 2001 From: Navid Rahimi Date: Sun, 19 Apr 2026 20:00:17 +0330 Subject: [PATCH 2/3] Add comprehensive Spark cryptographic component benchmarks with generic framework --- src/CMakeLists.txt | 4 +- src/bench/CMakeLists.txt | 85 ++++++++++ src/bench/benchmark.cpp | 225 +++++++++++++++++++++++++ src/bench/benchmark.h | 222 ++++++++++++++++++++++++ src/bench/checkblock.cpp | 6 +- src/bench/mempool_eviction.cpp | 2 +- src/bench/spark_bpplus.cpp | 298 +++++++++++++++++++++++++++++++++ src/bench/spark_chaum.cpp | 190 +++++++++++++++++++++ src/bench/spark_grootle.cpp | 267 +++++++++++++++++++++++++++++ src/bench/spark_schnorr.cpp | 171 +++++++++++++++++++ 10 files changed, 1466 insertions(+), 4 deletions(-) create mode 100644 src/bench/benchmark.cpp create mode 100644 src/bench/benchmark.h create mode 100644 src/bench/spark_bpplus.cpp create mode 100644 src/bench/spark_chaum.cpp create mode 100644 src/bench/spark_grootle.cpp create mode 100644 src/bench/spark_schnorr.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index aa80161a0d..be9931069b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -533,6 +533,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 +572,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 be3f593cb1..a033881ee7 100644 --- a/src/bench/CMakeLists.txt +++ b/src/bench/CMakeLists.txt @@ -46,3 +46,88 @@ add_test(NAME bench_sanity_check_high_priority install(TARGETS bench_firo RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) + +# Spark/Lelantus benchmarking framework +option(BUILD_BENCH_SPARK "Build Spark/Lelantus benchmarks" OFF) + +if(BUILD_BENCH_SPARK) + message(STATUS "Building Spark benchmarks") + + # Generic benchmarking framework library + add_library(firo_benchmark STATIC + ${CMAKE_CURRENT_SOURCE_DIR}/benchmark.cpp + ) + + target_include_directories(firo_benchmark PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ) + + # Optional: Enable Linux perf counters (requires explicit opt-in) + option(ENABLE_BENCH_PERF_COUNTERS "Enable Linux perf counters in benchmarks (Linux only)" OFF) + + if(ENABLE_BENCH_PERF_COUNTERS) + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + target_compile_definitions(firo_benchmark PUBLIC BENCH_PERF_COUNTERS) + message(STATUS " Linux perf counters: ENABLED") + else() + message(WARNING " ENABLE_BENCH_PERF_COUNTERS is only supported on Linux, ignoring") + endif() + else() + message(STATUS " Linux perf counters: DISABLED (use -DENABLE_BENCH_PERF_COUNTERS=ON to enable)") + endif() + + # Grootle proof benchmarks + add_executable(bench_spark_grootle + ${CMAKE_CURRENT_SOURCE_DIR}/spark_grootle.cpp + ) + + target_link_libraries(bench_spark_grootle + firo_benchmark + firo_node + Boost::filesystem + secp256k1pp + ) + + # BPPlus (Bulletproofs+) benchmarks + add_executable(bench_spark_bpplus + ${CMAKE_CURRENT_SOURCE_DIR}/spark_bpplus.cpp + ) + + target_link_libraries(bench_spark_bpplus + firo_benchmark + firo_node + Boost::filesystem + secp256k1pp + ) + + # Chaum proof benchmarks + add_executable(bench_spark_chaum + ${CMAKE_CURRENT_SOURCE_DIR}/spark_chaum.cpp + ) + + target_link_libraries(bench_spark_chaum + firo_benchmark + firo_node + Boost::filesystem + secp256k1pp + ) + + # Schnorr signature benchmarks + add_executable(bench_spark_schnorr + ${CMAKE_CURRENT_SOURCE_DIR}/spark_schnorr.cpp + ) + + target_link_libraries(bench_spark_schnorr + firo_benchmark + firo_node + Boost::filesystem + secp256k1pp + ) + + # Install benchmark executables + install(TARGETS bench_spark_grootle bench_spark_bpplus bench_spark_chaum bench_spark_schnorr + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ) + + message(STATUS " Benchmarks: bench_spark_grootle, bench_spark_bpplus, bench_spark_chaum, bench_spark_schnorr") +endif() diff --git a/src/bench/benchmark.cpp b/src/bench/benchmark.cpp new file mode 100644 index 0000000000..d73001bc9d --- /dev/null +++ b/src/bench/benchmark.cpp @@ -0,0 +1,225 @@ +// Copyright (c) 2025 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 "benchmark.h" +#include +#include + +#ifdef __linux__ +#include +#endif + +namespace benchmark { + +// BenchMetrics implementation +void BenchMetrics::print() const { + std::cout << "\n=== Benchmark: " << name << " ===\n"; + std::cout << "Iterations: " << iterations << "\n"; + std::cout << std::fixed << std::setprecision(2); + std::cout << "Time (ns):\n"; + std::cout << " Min: " << min_time_ns << " ns (" << std::setprecision(6) << (min_time_ns / 1e9) << " s)\n"; + std::cout << " Max: " << std::setprecision(2) << max_time_ns << " ns (" << std::setprecision(6) << (max_time_ns / 1e9) << " s)\n"; + std::cout << " Avg: " << std::setprecision(2) << avg_time_ns << " ns (" << std::setprecision(6) << (avg_time_ns / 1e9) << " s)\n"; + std::cout << " Median: " << std::setprecision(2) << median_time_ns << " ns (" << std::setprecision(6) << (median_time_ns / 1e9) << " s)\n"; + std::cout << " StdDev: " << stddev_time_ns << "\n"; + + if (avg_cycles > 0) { + std::cout << "Cycles:\n"; + std::cout << " Min: " << min_cycles << "\n"; + std::cout << " Max: " << max_cycles << "\n"; + std::cout << " Avg: " << avg_cycles << "\n"; + } + +#ifdef BENCH_PERF_COUNTERS + std::cout << "Performance Counters:\n"; + std::cout << " Cache Misses: " << cache_misses << "\n"; + std::cout << " Cache References: " << cache_references << "\n"; + if (cache_references > 0) { + double miss_rate = (double)cache_misses / cache_references * 100.0; + std::cout << " Cache Miss Rate: " << std::setprecision(2) << miss_rate << "%\n"; + } + std::cout << " Branch Misses: " << branch_misses << "\n"; + std::cout << " Instructions: " << instructions << "\n"; + if (avg_cycles > 0 && instructions > 0) { + // IPC = average instructions per iteration / average cycles per iteration + double avg_instructions = (double)instructions / iterations; + double ipc = avg_instructions / avg_cycles; + std::cout << " IPC: " << std::setprecision(2) << ipc << "\n"; + } +#endif + std::cout << std::endl; +} + +void BenchMetrics::print_csv_header() const { + std::cout << "name,iterations,min_ns,max_ns,avg_ns,median_ns,stddev_ns,min_cycles,max_cycles,avg_cycles"; +#ifdef BENCH_PERF_COUNTERS + std::cout << ",cache_misses,cache_refs,branch_misses,instructions"; +#endif + std::cout << "\n"; +} + +void BenchMetrics::print_csv() const { + std::cout << name << "," + << iterations << "," + << std::fixed << std::setprecision(2) + << min_time_ns << "," + << max_time_ns << "," + << avg_time_ns << "," + << median_time_ns << "," + << stddev_time_ns << "," + << min_cycles << "," + << max_cycles << "," + << avg_cycles; +#ifdef BENCH_PERF_COUNTERS + std::cout << "," << cache_misses + << "," << cache_references + << "," << branch_misses + << "," << instructions; +#endif + std::cout << "\n"; +} + +// PerfCounters implementation +#ifdef BENCH_PERF_COUNTERS +int PerfCounters::setup_perf_event(uint32_t type, uint64_t config) { + struct perf_event_attr pe; + memset(&pe, 0, sizeof(struct perf_event_attr)); + pe.type = type; + pe.size = sizeof(struct perf_event_attr); + pe.config = config; + pe.disabled = 1; + pe.exclude_kernel = 1; + pe.exclude_hv = 1; + + int fd = syscall(__NR_perf_event_open, &pe, 0, -1, -1, 0); + return fd; +} +#endif + +PerfCounters::PerfCounters() : cycles(0) { +#ifdef BENCH_PERF_COUNTERS + cache_misses = 0; + cache_references = 0; + branch_misses = 0; + instructions = 0; + + fd_cycles = setup_perf_event(PERF_TYPE_HARDWARE, PERF_COUNT_HW_CPU_CYCLES); + fd_cache_misses = setup_perf_event(PERF_TYPE_HARDWARE, PERF_COUNT_HW_CACHE_MISSES); + fd_cache_refs = setup_perf_event(PERF_TYPE_HARDWARE, PERF_COUNT_HW_CACHE_REFERENCES); + fd_branch_misses = setup_perf_event(PERF_TYPE_HARDWARE, PERF_COUNT_HW_BRANCH_MISSES); + fd_instructions = setup_perf_event(PERF_TYPE_HARDWARE, PERF_COUNT_HW_INSTRUCTIONS); +#endif +} + +PerfCounters::~PerfCounters() { +#ifdef BENCH_PERF_COUNTERS + if (fd_cycles >= 0) close(fd_cycles); + if (fd_cache_misses >= 0) close(fd_cache_misses); + if (fd_cache_refs >= 0) close(fd_cache_refs); + if (fd_branch_misses >= 0) close(fd_branch_misses); + if (fd_instructions >= 0) close(fd_instructions); +#endif +} + +void PerfCounters::start() { +#ifdef BENCH_PERF_COUNTERS + if (fd_cycles >= 0) { + ioctl(fd_cycles, PERF_EVENT_IOC_RESET, 0); + ioctl(fd_cycles, PERF_EVENT_IOC_ENABLE, 0); + } + if (fd_cache_misses >= 0) { + ioctl(fd_cache_misses, PERF_EVENT_IOC_RESET, 0); + ioctl(fd_cache_misses, PERF_EVENT_IOC_ENABLE, 0); + } + if (fd_cache_refs >= 0) { + ioctl(fd_cache_refs, PERF_EVENT_IOC_RESET, 0); + ioctl(fd_cache_refs, PERF_EVENT_IOC_ENABLE, 0); + } + if (fd_branch_misses >= 0) { + ioctl(fd_branch_misses, PERF_EVENT_IOC_RESET, 0); + ioctl(fd_branch_misses, PERF_EVENT_IOC_ENABLE, 0); + } + if (fd_instructions >= 0) { + ioctl(fd_instructions, PERF_EVENT_IOC_RESET, 0); + ioctl(fd_instructions, PERF_EVENT_IOC_ENABLE, 0); + } +#endif +} + +void PerfCounters::stop() { +#ifdef BENCH_PERF_COUNTERS + if (fd_cycles >= 0) { + ioctl(fd_cycles, PERF_EVENT_IOC_DISABLE, 0); + if (read(fd_cycles, &cycles, sizeof(uint64_t)) != static_cast(sizeof(uint64_t))) { + cycles = 0; + } + } + if (fd_cache_misses >= 0) { + ioctl(fd_cache_misses, PERF_EVENT_IOC_DISABLE, 0); + if (read(fd_cache_misses, &cache_misses, sizeof(uint64_t)) != static_cast(sizeof(uint64_t))) { + cache_misses = 0; + } + } + if (fd_cache_refs >= 0) { + ioctl(fd_cache_refs, PERF_EVENT_IOC_DISABLE, 0); + if (read(fd_cache_refs, &cache_references, sizeof(uint64_t)) != static_cast(sizeof(uint64_t))) { + cache_references = 0; + } + } + if (fd_branch_misses >= 0) { + ioctl(fd_branch_misses, PERF_EVENT_IOC_DISABLE, 0); + if (read(fd_branch_misses, &branch_misses, sizeof(uint64_t)) != static_cast(sizeof(uint64_t))) { + branch_misses = 0; + } + } + if (fd_instructions >= 0) { + ioctl(fd_instructions, PERF_EVENT_IOC_DISABLE, 0); + if (read(fd_instructions, &instructions, sizeof(uint64_t)) != static_cast(sizeof(uint64_t))) { + instructions = 0; + } + } +#endif +} + +// BenchRunner implementation +BenchRunner::BenchRunner(const std::string& benchmark_name, uint64_t min_iters, double min_time_secs) + : name(benchmark_name) + , warmup_iterations(3) + , min_iterations(min_iters) + , min_time_seconds(min_time_secs) +{ +} + +void BenchRunner::warmup(std::function func) { + for (uint64_t i = 0; i < warmup_iterations; ++i) { + func(); + } +} + +double BenchRunner::calculate_stddev(const std::vector& values, double mean) const { + if (values.size() <= 1) return 0.0; + + double sum_sq_diff = 0.0; + for (double v : values) { + double diff = v - mean; + sum_sq_diff += diff * diff; + } + + return std::sqrt(sum_sq_diff / (values.size() - 1)); +} + +double BenchRunner::calculate_median(std::vector values) const { + if (values.empty()) return 0.0; + + std::sort(values.begin(), values.end()); + size_t n = values.size(); + + if (n % 2 == 0) { + return (values[n/2 - 1] + values[n/2]) / 2.0; + } else { + return values[n/2]; + } +} + +} // namespace benchmark diff --git a/src/bench/benchmark.h b/src/bench/benchmark.h new file mode 100644 index 0000000000..4b6f9834e1 --- /dev/null +++ b/src/bench/benchmark.h @@ -0,0 +1,222 @@ +// Copyright (c) 2025 The Firo Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef FIRO_BENCH_BENCHMARK_H +#define FIRO_BENCH_BENCHMARK_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Platform-specific performance counter support +// Only enabled if explicitly requested via -DBENCH_PERF_COUNTERS +#ifdef BENCH_PERF_COUNTERS + #ifdef __linux__ + #include + #include + #include + #include + #else + #error "BENCH_PERF_COUNTERS is only supported on Linux" + #endif +#endif + +namespace benchmark { + +// Benchmark metrics structure +struct BenchMetrics { + std::string name; + uint64_t iterations; + + // Timing metrics (always available) + double min_time_ns; + double max_time_ns; + double avg_time_ns; + double median_time_ns; + double stddev_time_ns; + + // CPU cycles (platform-dependent) + uint64_t min_cycles; + uint64_t max_cycles; + uint64_t avg_cycles; + + // Memory metrics (if available) + size_t peak_memory_bytes; + +#ifdef BENCH_PERF_COUNTERS + // Linux perf counters (only if explicitly enabled) + uint64_t cache_misses; + uint64_t cache_references; + uint64_t branch_misses; + uint64_t instructions; +#endif + + void print() const; + void print_csv_header() const; + void print_csv() const; +}; + +// Performance counter wrapper +class PerfCounters { +public: + PerfCounters(); + ~PerfCounters(); + + void start(); + void stop(); + + uint64_t get_cycles() const { return cycles; } + +#ifdef BENCH_PERF_COUNTERS + uint64_t get_cache_misses() const { return cache_misses; } + uint64_t get_cache_references() const { return cache_references; } + uint64_t get_branch_misses() const { return branch_misses; } + uint64_t get_instructions() const { return instructions; } +#endif + +private: + uint64_t cycles; + +#ifdef BENCH_PERF_COUNTERS + int fd_cycles; + int fd_cache_misses; + int fd_cache_refs; + int fd_branch_misses; + int fd_instructions; + + uint64_t cache_misses; + uint64_t cache_references; + uint64_t branch_misses; + uint64_t instructions; + + int setup_perf_event(uint32_t type, uint64_t config); +#endif +}; + +// Benchmark runner +class BenchRunner { +public: + BenchRunner(const std::string& name, uint64_t min_iterations = 10, double min_time_seconds = 1.0); + + // Run benchmark function and collect metrics + template + BenchMetrics run(Func&& func); + + void set_warmup_iterations(uint64_t warmup) { warmup_iterations = warmup; } + void set_min_iterations(uint64_t min_iter) { min_iterations = min_iter; } + void set_min_time(double seconds) { min_time_seconds = seconds; } + +private: + std::string name; + uint64_t warmup_iterations; + uint64_t min_iterations; + double min_time_seconds; + + void warmup(std::function func); + double calculate_stddev(const std::vector& values, double mean) const; + double calculate_median(std::vector values) const; +}; + +// Template implementation +template +BenchMetrics BenchRunner::run(Func&& func) { + BenchMetrics metrics = {}; + metrics.name = name; + + // Warmup phase + std::function func_wrapper = func; + warmup(func_wrapper); + + // Measurement phase + std::vector times; + std::vector cycles_vec; + times.reserve(min_iterations * 2); + cycles_vec.reserve(min_iterations * 2); + +#ifdef BENCH_PERF_COUNTERS + uint64_t total_cache_misses = 0; + uint64_t total_cache_refs = 0; + uint64_t total_branch_misses = 0; + uint64_t total_instructions = 0; +#endif + + uint64_t total_iterations = 0; + auto start_time = std::chrono::steady_clock::now(); + + PerfCounters perf; + + while (total_iterations < min_iterations || + std::chrono::duration(std::chrono::steady_clock::now() - start_time).count() < min_time_seconds) { + + perf.start(); + auto t1 = std::chrono::high_resolution_clock::now(); + + func(); + + auto t2 = std::chrono::high_resolution_clock::now(); + perf.stop(); + + double elapsed_ns = std::chrono::duration(t2 - t1).count(); + times.push_back(elapsed_ns); + cycles_vec.push_back(perf.get_cycles()); + +#ifdef BENCH_PERF_COUNTERS + total_cache_misses += perf.get_cache_misses(); + total_cache_refs += perf.get_cache_references(); + total_branch_misses += perf.get_branch_misses(); + total_instructions += perf.get_instructions(); +#endif + + total_iterations++; + } + + // Calculate statistics + metrics.iterations = total_iterations; + + metrics.min_time_ns = *std::min_element(times.begin(), times.end()); + metrics.max_time_ns = *std::max_element(times.begin(), times.end()); + + double sum = 0.0; + for (double t : times) sum += t; + metrics.avg_time_ns = sum / times.size(); + + metrics.median_time_ns = calculate_median(times); + metrics.stddev_time_ns = calculate_stddev(times, metrics.avg_time_ns); + + if (!cycles_vec.empty()) { + metrics.min_cycles = *std::min_element(cycles_vec.begin(), cycles_vec.end()); + metrics.max_cycles = *std::max_element(cycles_vec.begin(), cycles_vec.end()); + + uint64_t sum_cycles = 0; + for (uint64_t c : cycles_vec) sum_cycles += c; + metrics.avg_cycles = sum_cycles / cycles_vec.size(); + } + +#ifdef BENCH_PERF_COUNTERS + metrics.cache_misses = total_cache_misses; + metrics.cache_references = total_cache_refs; + metrics.branch_misses = total_branch_misses; + metrics.instructions = total_instructions; +#endif + + return metrics; +} + +// Macro for easy benchmark registration +#define BENCHMARK(name, func) \ + static void bench_##name() { \ + benchmark::BenchRunner runner(#name); \ + auto metrics = runner.run(func); \ + metrics.print(); \ + } + +} // namespace benchmark + +#endif // FIRO_BENCH_BENCHMARK_H diff --git a/src/bench/checkblock.cpp b/src/bench/checkblock.cpp index 8ec965c376..694f17e543 100644 --- a/src/bench/checkblock.cpp +++ b/src/bench/checkblock.cpp @@ -26,7 +26,8 @@ static void DeserializeBlockTest(benchmark::State& state) while (state.KeepRunning()) { CBlock block; stream >> block; - assert(stream.Rewind(benchmark::data::block413567.size())); + bool rewound = stream.Rewind(benchmark::data::block413567.size()); + assert(rewound); } } @@ -43,7 +44,8 @@ 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(benchmark::data::block413567.size())); + bool rewound = stream.Rewind(benchmark::data::block413567.size()); + assert(rewound); CValidationState validationState; assert(CheckBlock(block, validationState, params)); 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..221d8c8ea8 --- /dev/null +++ b/src/bench/spark_bpplus.cpp @@ -0,0 +1,298 @@ +// Copyright (c) 2025 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 "benchmark.h" +#include "../libspark/bpplus.h" +#include "../libspark/params.h" +#include + +using namespace spark; +using namespace benchmark; + +// Helper to generate test data for BPPlus range proofs +struct BPPlusTestData { + std::size_t N; // bit length + std::size_t num_outputs; + + GroupElement G, H; + std::vector Gi, Hi; + + std::vector v; // values + std::vector r; // randomness + std::vector C; // commitments + + BPPlusTestData(std::size_t N_, std::size_t num_outputs_) : N(N_), num_outputs(num_outputs_) { + // Generate generators + G.randomize(); + H.randomize(); + + // Pad num_outputs to next power of 2 if needed + std::size_t M = num_outputs; + if ((M & (M - 1)) != 0) { + M = 1 << (64 - __builtin_clzll(M)); + } + + Gi.resize(N * M); + Hi.resize(N * M); + for (std::size_t i = 0; i < N * M; ++i) { + Gi[i].randomize(); + Hi[i].randomize(); + } + + // Generate values and commitments + v.resize(num_outputs); + r.resize(num_outputs); + C.resize(num_outputs); + + for (std::size_t i = 0; i < num_outputs; ++i) { + // Generate random value within range + uint64_t val = rand() % (1ULL << std::min(N, 32UL)); + v[i] = Scalar(val); + r[i].randomize(); + + // Commitment: C = vG + rH + C[i] = G * v[i] + H * r[i]; + } + } +}; + +// Benchmark BPPlus proof generation with different output counts +void bench_bpplus_prove_1output() { + BPPlusTestData data(64, 1); + BPPlus bpplus(data.G, data.H, data.Gi, data.Hi, data.N); + + BenchRunner runner("BPPlus_Prove_1Output_64bit", 10, 1.0); + auto metrics = runner.run([&]() { + BPPlusProof proof; + bpplus.prove(data.v, data.r, data.C, proof); + }); + metrics.print(); +} + +void bench_bpplus_prove_2outputs() { + BPPlusTestData data(64, 2); + BPPlus bpplus(data.G, data.H, data.Gi, data.Hi, data.N); + + BenchRunner runner("BPPlus_Prove_2Outputs_64bit", 10, 1.0); + auto metrics = runner.run([&]() { + BPPlusProof proof; + bpplus.prove(data.v, data.r, data.C, proof); + }); + metrics.print(); +} + +void bench_bpplus_prove_4outputs() { + BPPlusTestData data(64, 4); + BPPlus bpplus(data.G, data.H, data.Gi, data.Hi, data.N); + + BenchRunner runner("BPPlus_Prove_4Outputs_64bit", 10, 1.0); + auto metrics = runner.run([&]() { + BPPlusProof proof; + bpplus.prove(data.v, data.r, data.C, proof); + }); + metrics.print(); +} + +void bench_bpplus_prove_8outputs() { + BPPlusTestData data(64, 8); + BPPlus bpplus(data.G, data.H, data.Gi, data.Hi, data.N); + + BenchRunner runner("BPPlus_Prove_8Outputs_64bit", 5, 1.0); + auto metrics = runner.run([&]() { + BPPlusProof proof; + bpplus.prove(data.v, data.r, data.C, proof); + }); + metrics.print(); +} + +// Benchmark with different bit lengths +void bench_bpplus_prove_32bit() { + BPPlusTestData data(32, 2); + BPPlus bpplus(data.G, data.H, data.Gi, data.Hi, data.N); + + BenchRunner runner("BPPlus_Prove_2Outputs_32bit", 10, 1.0); + auto metrics = runner.run([&]() { + BPPlusProof proof; + bpplus.prove(data.v, data.r, data.C, proof); + }); + metrics.print(); +} + +void bench_bpplus_prove_128bit() { + BPPlusTestData data(128, 2); + BPPlus bpplus(data.G, data.H, data.Gi, data.Hi, data.N); + + BenchRunner runner("BPPlus_Prove_2Outputs_128bit", 5, 1.0); + auto metrics = runner.run([&]() { + BPPlusProof proof; + bpplus.prove(data.v, data.r, data.C, proof); + }); + metrics.print(); +} + +// Benchmark BPPlus proof verification +void bench_bpplus_verify_1output() { + BPPlusTestData data(64, 1); + BPPlus bpplus(data.G, data.H, data.Gi, data.Hi, data.N); + + BPPlusProof proof; + bpplus.prove(data.v, data.r, data.C, proof); + + BenchRunner runner("BPPlus_Verify_1Output_64bit", 50, 1.0); + auto metrics = runner.run([&]() { + bool result = bpplus.verify(data.C, proof); + if (!result) { + std::cerr << "Verification failed!\n"; + } + }); + metrics.print(); +} + +void bench_bpplus_verify_2outputs() { + BPPlusTestData data(64, 2); + BPPlus bpplus(data.G, data.H, data.Gi, data.Hi, data.N); + + BPPlusProof proof; + bpplus.prove(data.v, data.r, data.C, proof); + + BenchRunner runner("BPPlus_Verify_2Outputs_64bit", 50, 1.0); + auto metrics = runner.run([&]() { + bool result = bpplus.verify(data.C, proof); + if (!result) { + std::cerr << "Verification failed!\n"; + } + }); + metrics.print(); +} + +void bench_bpplus_verify_4outputs() { + BPPlusTestData data(64, 4); + BPPlus bpplus(data.G, data.H, data.Gi, data.Hi, data.N); + + BPPlusProof proof; + bpplus.prove(data.v, data.r, data.C, proof); + + BenchRunner runner("BPPlus_Verify_4Outputs_64bit", 50, 1.0); + auto metrics = runner.run([&]() { + bool result = bpplus.verify(data.C, proof); + if (!result) { + std::cerr << "Verification failed!\n"; + } + }); + metrics.print(); +} + +void bench_bpplus_verify_8outputs() { + BPPlusTestData data(64, 8); + BPPlus bpplus(data.G, data.H, data.Gi, data.Hi, data.N); + + BPPlusProof proof; + bpplus.prove(data.v, data.r, data.C, proof); + + BenchRunner runner("BPPlus_Verify_8Outputs_64bit", 30, 1.0); + auto metrics = runner.run([&]() { + bool result = bpplus.verify(data.C, proof); + if (!result) { + std::cerr << "Verification failed!\n"; + } + }); + metrics.print(); +} + +// Benchmark batch verification +void bench_bpplus_batch_verify_10proofs() { + const size_t num_proofs = 10; + BPPlusTestData data(64, 2); + BPPlus bpplus(data.G, data.H, data.Gi, data.Hi, data.N); + + // Generate multiple proofs + std::vector proofs; + std::vector> C_vec; + + for (size_t i = 0; i < num_proofs; ++i) { + // Generate independent values and commitments for each proof + std::vector v_i(2), r_i(2); + std::vector C_i(2); + for (size_t j = 0; j < 2; ++j) { + uint64_t val = rand() % (1ULL << 32); + v_i[j] = Scalar(val); + r_i[j].randomize(); + C_i[j] = data.G * v_i[j] + data.H * r_i[j]; + } + BPPlusProof proof; + bpplus.prove(v_i, r_i, C_i, proof); + proofs.push_back(proof); + C_vec.push_back(C_i); + } + + BenchRunner runner("BPPlus_BatchVerify_10Proofs_2Outputs", 20, 1.0); + auto metrics = runner.run([&]() { + bool result = bpplus.verify(C_vec, proofs); + if (!result) { + std::cerr << "Batch verification failed!\n"; + } + }); + metrics.print(); +} + +void bench_bpplus_batch_verify_50proofs() { + const size_t num_proofs = 50; + BPPlusTestData data(64, 2); + BPPlus bpplus(data.G, data.H, data.Gi, data.Hi, data.N); + + // Generate multiple proofs + std::vector proofs; + std::vector> C_vec; + + for (size_t i = 0; i < num_proofs; ++i) { + // Generate independent values and commitments for each proof + std::vector v_i(2), r_i(2); + std::vector C_i(2); + for (size_t j = 0; j < 2; ++j) { + uint64_t val = rand() % (1ULL << 32); + v_i[j] = Scalar(val); + r_i[j].randomize(); + C_i[j] = data.G * v_i[j] + data.H * r_i[j]; + } + BPPlusProof proof; + bpplus.prove(v_i, r_i, C_i, proof); + proofs.push_back(proof); + C_vec.push_back(C_i); + } + + BenchRunner runner("BPPlus_BatchVerify_50Proofs_2Outputs", 10, 1.0); + auto metrics = runner.run([&]() { + bool result = bpplus.verify(C_vec, proofs); + if (!result) { + std::cerr << "Batch verification failed!\n"; + } + }); + metrics.print(); +} + +int main() { + std::cout << "=== Spark BPPlus (Bulletproofs+) Benchmarks ===\n\n"; + + std::cout << "--- Proof Generation (varying output count) ---\n"; + bench_bpplus_prove_1output(); + bench_bpplus_prove_2outputs(); + bench_bpplus_prove_4outputs(); + bench_bpplus_prove_8outputs(); + + std::cout << "\n--- Proof Generation (varying bit length) ---\n"; + bench_bpplus_prove_32bit(); + bench_bpplus_prove_128bit(); + + std::cout << "\n--- Proof Verification ---\n"; + bench_bpplus_verify_1output(); + bench_bpplus_verify_2outputs(); + bench_bpplus_verify_4outputs(); + bench_bpplus_verify_8outputs(); + + std::cout << "\n--- Batch Verification ---\n"; + bench_bpplus_batch_verify_10proofs(); + bench_bpplus_batch_verify_50proofs(); + + return 0; +} diff --git a/src/bench/spark_chaum.cpp b/src/bench/spark_chaum.cpp new file mode 100644 index 0000000000..df5def83b1 --- /dev/null +++ b/src/bench/spark_chaum.cpp @@ -0,0 +1,190 @@ +// Copyright (c) 2025 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 "benchmark.h" +#include "../libspark/chaum.h" +#include + +using namespace spark; +using namespace benchmark; + +// Helper to generate test data for Chaum proofs +struct ChaumTestData { + std::size_t n; // number of commitments + + GroupElement F, G, H, U; + Scalar mu; + + std::vector x, y, z; + std::vector S, T; + + ChaumTestData(std::size_t n_) : n(n_) { + // Generate generators + F.randomize(); + G.randomize(); + H.randomize(); + U.randomize(); + + // Generate scalar mu + mu.randomize(); + + // Generate witness scalars + x.resize(n); + y.resize(n); + z.resize(n); + + for (std::size_t i = 0; i < n; ++i) { + x[i].randomize(); + y[i].randomize(); + z[i].randomize(); + } + + // Generate commitments S and T + // S[i] = F^x[i] * G^y[i] * H^z[i] + // T[i] = U^x[i] * G^(mu*y[i]) + S.resize(n); + T.resize(n); + + for (std::size_t i = 0; i < n; ++i) { + S[i] = F * x[i] + G * y[i] + H * z[i]; + T[i] = (U + G * y[i].negate()) * x[i].inverse(); + } + } +}; + +// Benchmark Chaum proof generation with different commitment counts +void bench_chaum_prove_1commitment() { + ChaumTestData data(1); + Chaum chaum(data.F, data.G, data.H, data.U); + + BenchRunner runner("Chaum_Prove_1Commitment", 100, 1.0); + auto metrics = runner.run([&]() { + ChaumProof proof; + chaum.prove(data.mu, data.x, data.y, data.z, data.S, data.T, proof); + }); + metrics.print(); +} + +void bench_chaum_prove_2commitments() { + ChaumTestData data(2); + Chaum chaum(data.F, data.G, data.H, data.U); + + BenchRunner runner("Chaum_Prove_2Commitments", 100, 1.0); + auto metrics = runner.run([&]() { + ChaumProof proof; + chaum.prove(data.mu, data.x, data.y, data.z, data.S, data.T, proof); + }); + metrics.print(); +} + +void bench_chaum_prove_4commitments() { + ChaumTestData data(4); + Chaum chaum(data.F, data.G, data.H, data.U); + + BenchRunner runner("Chaum_Prove_4Commitments", 100, 1.0); + auto metrics = runner.run([&]() { + ChaumProof proof; + chaum.prove(data.mu, data.x, data.y, data.z, data.S, data.T, proof); + }); + metrics.print(); +} + +void bench_chaum_prove_8commitments() { + ChaumTestData data(8); + Chaum chaum(data.F, data.G, data.H, data.U); + + BenchRunner runner("Chaum_Prove_8Commitments", 50, 1.0); + auto metrics = runner.run([&]() { + ChaumProof proof; + chaum.prove(data.mu, data.x, data.y, data.z, data.S, data.T, proof); + }); + metrics.print(); +} + +// Benchmark Chaum proof verification +void bench_chaum_verify_1commitment() { + ChaumTestData data(1); + Chaum chaum(data.F, data.G, data.H, data.U); + + ChaumProof proof; + chaum.prove(data.mu, data.x, data.y, data.z, data.S, data.T, proof); + + BenchRunner runner("Chaum_Verify_1Commitment", 500, 1.0); + auto metrics = runner.run([&]() { + bool result = chaum.verify(data.mu, data.S, data.T, proof); + if (!result) { + std::cerr << "Verification failed!\n"; + } + }); + metrics.print(); +} + +void bench_chaum_verify_2commitments() { + ChaumTestData data(2); + Chaum chaum(data.F, data.G, data.H, data.U); + + ChaumProof proof; + chaum.prove(data.mu, data.x, data.y, data.z, data.S, data.T, proof); + + BenchRunner runner("Chaum_Verify_2Commitments", 500, 1.0); + auto metrics = runner.run([&]() { + bool result = chaum.verify(data.mu, data.S, data.T, proof); + if (!result) { + std::cerr << "Verification failed!\n"; + } + }); + metrics.print(); +} + +void bench_chaum_verify_4commitments() { + ChaumTestData data(4); + Chaum chaum(data.F, data.G, data.H, data.U); + + ChaumProof proof; + chaum.prove(data.mu, data.x, data.y, data.z, data.S, data.T, proof); + + BenchRunner runner("Chaum_Verify_4Commitments", 500, 1.0); + auto metrics = runner.run([&]() { + bool result = chaum.verify(data.mu, data.S, data.T, proof); + if (!result) { + std::cerr << "Verification failed!\n"; + } + }); + metrics.print(); +} + +void bench_chaum_verify_8commitments() { + ChaumTestData data(8); + Chaum chaum(data.F, data.G, data.H, data.U); + + ChaumProof proof; + chaum.prove(data.mu, data.x, data.y, data.z, data.S, data.T, proof); + + BenchRunner runner("Chaum_Verify_8Commitments", 500, 1.0); + auto metrics = runner.run([&]() { + bool result = chaum.verify(data.mu, data.S, data.T, proof); + if (!result) { + std::cerr << "Verification failed!\n"; + } + }); + metrics.print(); +} + +int main() { + std::cout << "=== Spark Chaum Proof Benchmarks ===\n\n"; + + std::cout << "--- Proof Generation ---\n"; + bench_chaum_prove_1commitment(); + bench_chaum_prove_2commitments(); + bench_chaum_prove_4commitments(); + bench_chaum_prove_8commitments(); + + std::cout << "\n--- Proof Verification ---\n"; + bench_chaum_verify_1commitment(); + bench_chaum_verify_2commitments(); + bench_chaum_verify_4commitments(); + bench_chaum_verify_8commitments(); + + return 0; +} diff --git a/src/bench/spark_grootle.cpp b/src/bench/spark_grootle.cpp new file mode 100644 index 0000000000..fd243fb77c --- /dev/null +++ b/src/bench/spark_grootle.cpp @@ -0,0 +1,267 @@ +// Copyright (c) 2025 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 "benchmark.h" +#include "../libspark/grootle.h" +#include "../libspark/params.h" +#include "../random.h" +#include + +using namespace spark; +using namespace benchmark; + +// Helper to generate test data for Grootle proofs +struct GrootleTestData { + std::size_t n; + std::size_t m; + std::size_t set_size; + std::size_t l; // hidden index + + 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(std::size_t n_, std::size_t m_) : n(n_), m(m_) { + set_size = 1; + for (std::size_t i = 0; i < m; ++i) { + set_size *= n; + } + + // Generate random hidden index + l = rand() % set_size; + + // Generate generators + 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(); + } + + // Generate witness + s.randomize(); + v.randomize(); + + // Generate sets S and V + S.resize(set_size); + V.resize(set_size); + for (std::size_t i = 0; i < set_size; ++i) { + S[i].randomize(); + V[i].randomize(); + } + + // Set the actual values at hidden index + S1 = S[l] + H * s.negate(); + V1 = V[l] + H * v.negate(); + + // Generate random root + root.resize(32); + GetRandBytes(root.data(), 32); + } +}; + +// Benchmark Grootle proof generation with different set sizes +void bench_grootle_prove_n4_m2() { + GrootleTestData data(4, 2); // set size = 16 + Grootle grootle(data.H, data.Gi, data.Hi, data.n, data.m); + + BenchRunner runner("Grootle_Prove_N4_M2_SetSize16", 10, 1.0); + auto metrics = runner.run([&]() { + GrootleProof proof; + grootle.prove(data.l, data.s, data.S, data.S1, data.v, data.V, data.V1, data.root, proof); + }); + metrics.print(); +} + +void bench_grootle_prove_n4_m3() { + GrootleTestData data(4, 3); // set size = 64 + Grootle grootle(data.H, data.Gi, data.Hi, data.n, data.m); + + BenchRunner runner("Grootle_Prove_N4_M3_SetSize64", 10, 1.0); + auto metrics = runner.run([&]() { + GrootleProof proof; + grootle.prove(data.l, data.s, data.S, data.S1, data.v, data.V, data.V1, data.root, proof); + }); + metrics.print(); +} + +void bench_grootle_prove_n4_m4() { + GrootleTestData data(4, 4); // set size = 256 + Grootle grootle(data.H, data.Gi, data.Hi, data.n, data.m); + + BenchRunner runner("Grootle_Prove_N4_M4_SetSize256", 10, 1.0); + auto metrics = runner.run([&]() { + GrootleProof proof; + grootle.prove(data.l, data.s, data.S, data.S1, data.v, data.V, data.V1, data.root, proof); + }); + metrics.print(); +} + +void bench_grootle_prove_n8_m3() { + GrootleTestData data(8, 3); // set size = 512 + Grootle grootle(data.H, data.Gi, data.Hi, data.n, data.m); + + BenchRunner runner("Grootle_Prove_N8_M3_SetSize512", 5, 1.0); + auto metrics = runner.run([&]() { + GrootleProof proof; + grootle.prove(data.l, data.s, data.S, data.S1, data.v, data.V, data.V1, data.root, proof); + }); + metrics.print(); +} + +void bench_grootle_prove_n16_m3() { + GrootleTestData data(16, 3); // set size = 4096 + Grootle grootle(data.H, data.Gi, data.Hi, data.n, data.m); + + BenchRunner runner("Grootle_Prove_N16_M3_SetSize4096", 3, 1.0); + auto metrics = runner.run([&]() { + GrootleProof proof; + grootle.prove(data.l, data.s, data.S, data.S1, data.v, data.V, data.V1, data.root, proof); + }); + metrics.print(); +} + +// Benchmark Grootle proof verification +void bench_grootle_verify_n4_m2() { + GrootleTestData data(4, 2); + Grootle grootle(data.H, data.Gi, data.Hi, data.n, data.m); + + GrootleProof proof; + grootle.prove(data.l, data.s, data.S, data.S1, data.v, data.V, data.V1, data.root, proof); + + BenchRunner runner("Grootle_Verify_N4_M2_SetSize16", 50, 1.0); + auto metrics = runner.run([&]() { + bool result = grootle.verify(data.S, data.S1, data.V, data.V1, data.root, data.set_size, proof); + if (!result) { + std::cerr << "Verification failed!\n"; + } + }); + metrics.print(); +} + +void bench_grootle_verify_n4_m3() { + GrootleTestData data(4, 3); + Grootle grootle(data.H, data.Gi, data.Hi, data.n, data.m); + + GrootleProof proof; + grootle.prove(data.l, data.s, data.S, data.S1, data.v, data.V, data.V1, data.root, proof); + + BenchRunner runner("Grootle_Verify_N4_M3_SetSize64", 50, 1.0); + auto metrics = runner.run([&]() { + bool result = grootle.verify(data.S, data.S1, data.V, data.V1, data.root, data.set_size, proof); + if (!result) { + std::cerr << "Verification failed!\n"; + } + }); + metrics.print(); +} + +void bench_grootle_verify_n4_m4() { + GrootleTestData data(4, 4); + Grootle grootle(data.H, data.Gi, data.Hi, data.n, data.m); + + GrootleProof proof; + grootle.prove(data.l, data.s, data.S, data.S1, data.v, data.V, data.V1, data.root, proof); + + BenchRunner runner("Grootle_Verify_N4_M4_SetSize256", 50, 1.0); + auto metrics = runner.run([&]() { + bool result = grootle.verify(data.S, data.S1, data.V, data.V1, data.root, data.set_size, proof); + if (!result) { + std::cerr << "Verification failed!\n"; + } + }); + metrics.print(); +} + +void bench_grootle_verify_n16_m3() { + GrootleTestData data(16, 3); + Grootle grootle(data.H, data.Gi, data.Hi, data.n, data.m); + + GrootleProof proof; + grootle.prove(data.l, data.s, data.S, data.S1, data.v, data.V, data.V1, data.root, proof); + + BenchRunner runner("Grootle_Verify_N16_M3_SetSize4096", 20, 1.0); + auto metrics = runner.run([&]() { + bool result = grootle.verify(data.S, data.S1, data.V, data.V1, data.root, data.set_size, proof); + if (!result) { + std::cerr << "Verification failed!\n"; + } + }); + metrics.print(); +} + +// Benchmark batch verification +void bench_grootle_batch_verify_10proofs() { + const size_t num_proofs = 10; + GrootleTestData data(4, 3); // set size = 64 + Grootle grootle(data.H, data.Gi, data.Hi, data.n, data.m); + + // Generate multiple proofs + std::vector proofs; + std::vector S1_vec; + std::vector V1_vec; + std::vector> roots; + std::vector sizes; + + for (size_t i = 0; i < num_proofs; ++i) { + // Generate independent witness for each proof, but use same anonymity set + size_t l = rand() % data.set_size; + Scalar s, v; + s.randomize(); + v.randomize(); + + GroupElement S1 = data.S[l] + data.H * s.negate(); + GroupElement V1 = data.V[l] + data.H * v.negate(); + + GrootleProof proof; + grootle.prove(l, s, data.S, S1, v, data.V, V1, data.root, proof); + proofs.push_back(proof); + S1_vec.push_back(S1); + V1_vec.push_back(V1); + roots.push_back(data.root); + sizes.push_back(data.set_size); + } + + BenchRunner runner("Grootle_BatchVerify_10Proofs_SetSize64", 20, 1.0); + auto metrics = runner.run([&]() { + bool result = grootle.verify(data.S, S1_vec, data.V, V1_vec, roots, sizes, proofs); + if (!result) { + std::cerr << "Batch verification failed!\n"; + } + }); + metrics.print(); +} + +int main() { + std::cout << "=== Spark Grootle Proof Benchmarks ===\n\n"; + + std::cout << "--- Proof Generation ---\n"; + bench_grootle_prove_n4_m2(); + bench_grootle_prove_n4_m3(); + bench_grootle_prove_n4_m4(); + bench_grootle_prove_n8_m3(); + bench_grootle_prove_n16_m3(); + + std::cout << "\n--- Proof Verification ---\n"; + bench_grootle_verify_n4_m2(); + bench_grootle_verify_n4_m3(); + bench_grootle_verify_n4_m4(); + bench_grootle_verify_n16_m3(); + + std::cout << "\n--- Batch Verification ---\n"; + bench_grootle_batch_verify_10proofs(); + + return 0; +} diff --git a/src/bench/spark_schnorr.cpp b/src/bench/spark_schnorr.cpp new file mode 100644 index 0000000000..64c505d629 --- /dev/null +++ b/src/bench/spark_schnorr.cpp @@ -0,0 +1,171 @@ +// Copyright (c) 2025 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 "benchmark.h" +#include "../libspark/schnorr.h" +#include + +using namespace spark; +using namespace benchmark; + +// Helper to generate test data for Schnorr signatures +struct SchnorrTestData { + std::size_t n; // number of keys + + GroupElement G; + std::vector y; + std::vector Y; + + SchnorrTestData(std::size_t n_) : n(n_) { + // Generate generator + G.randomize(); + + // Generate key pairs + y.resize(n); + Y.resize(n); + + for (std::size_t i = 0; i < n; ++i) { + y[i].randomize(); + Y[i] = G * y[i]; + } + } +}; + +// Benchmark Schnorr signature generation (single key) +void bench_schnorr_prove_1key() { + SchnorrTestData data(1); + Schnorr schnorr(data.G); + + BenchRunner runner("Schnorr_Sign_1Key", 500, 1.0); + auto metrics = runner.run([&]() { + SchnorrProof proof; + schnorr.prove(data.y[0], data.Y[0], proof); + }); + metrics.print(); +} + +// Benchmark Schnorr signature generation (multiple keys) +void bench_schnorr_prove_2keys() { + SchnorrTestData data(2); + Schnorr schnorr(data.G); + + BenchRunner runner("Schnorr_Sign_2Keys", 500, 1.0); + auto metrics = runner.run([&]() { + SchnorrProof proof; + schnorr.prove(data.y, data.Y, proof); + }); + metrics.print(); +} + +void bench_schnorr_prove_4keys() { + SchnorrTestData data(4); + Schnorr schnorr(data.G); + + BenchRunner runner("Schnorr_Sign_4Keys", 500, 1.0); + auto metrics = runner.run([&]() { + SchnorrProof proof; + schnorr.prove(data.y, data.Y, proof); + }); + metrics.print(); +} + +void bench_schnorr_prove_8keys() { + SchnorrTestData data(8); + Schnorr schnorr(data.G); + + BenchRunner runner("Schnorr_Sign_8Keys", 500, 1.0); + auto metrics = runner.run([&]() { + SchnorrProof proof; + schnorr.prove(data.y, data.Y, proof); + }); + metrics.print(); +} + +// Benchmark Schnorr signature verification (single key) +void bench_schnorr_verify_1key() { + SchnorrTestData data(1); + Schnorr schnorr(data.G); + + SchnorrProof proof; + schnorr.prove(data.y[0], data.Y[0], proof); + + BenchRunner runner("Schnorr_Verify_1Key", 1000, 1.0); + auto metrics = runner.run([&]() { + bool result = schnorr.verify(data.Y[0], proof); + if (!result) { + std::cerr << "Verification failed!\n"; + } + }); + metrics.print(); +} + +// Benchmark Schnorr signature verification (multiple keys) +void bench_schnorr_verify_2keys() { + SchnorrTestData data(2); + Schnorr schnorr(data.G); + + SchnorrProof proof; + schnorr.prove(data.y, data.Y, proof); + + BenchRunner runner("Schnorr_Verify_2Keys", 1000, 1.0); + auto metrics = runner.run([&]() { + bool result = schnorr.verify(data.Y, proof); + if (!result) { + std::cerr << "Verification failed!\n"; + } + }); + metrics.print(); +} + +void bench_schnorr_verify_4keys() { + SchnorrTestData data(4); + Schnorr schnorr(data.G); + + SchnorrProof proof; + schnorr.prove(data.y, data.Y, proof); + + BenchRunner runner("Schnorr_Verify_4Keys", 1000, 1.0); + auto metrics = runner.run([&]() { + bool result = schnorr.verify(data.Y, proof); + if (!result) { + std::cerr << "Verification failed!\n"; + } + }); + metrics.print(); +} + +void bench_schnorr_verify_8keys() { + SchnorrTestData data(8); + Schnorr schnorr(data.G); + + SchnorrProof proof; + schnorr.prove(data.y, data.Y, proof); + + BenchRunner runner("Schnorr_Verify_8Keys", 1000, 1.0); + auto metrics = runner.run([&]() { + bool result = schnorr.verify(data.Y, proof); + if (!result) { + std::cerr << "Verification failed!\n"; + } + }); + metrics.print(); +} + +int main() { + std::cout << "=== Spark Schnorr Signature Benchmarks ===\n\n"; + + std::cout << "--- Signature Generation ---\n"; + bench_schnorr_prove_1key(); + bench_schnorr_prove_2keys(); + bench_schnorr_prove_4keys(); + bench_schnorr_prove_8keys(); + + std::cout << "\n--- Signature Verification ---\n"; + bench_schnorr_verify_1key(); + bench_schnorr_verify_2keys(); + bench_schnorr_verify_4keys(); + bench_schnorr_verify_8keys(); + + return 0; +} From 95842afec001c53f7f5b4ecb379f162fff1f39aa Mon Sep 17 00:00:00 2001 From: Navid Rahimi Date: Mon, 1 Jun 2026 17:43:49 +0100 Subject: [PATCH 3/3] bench: integrate Spark component benchmarks into bench_firo --- .github/workflows/ci-master.yml | 8 + CMakeLists.txt | 7 +- cmake/script/GenerateHeaderFromRaw.cmake | 3 + cmake/utilities.cmake | 15 +- src/CMakeLists.txt | 3 +- src/bench/CMakeLists.txt | 87 +---- src/bench/bench_bitcoin.cpp | 22 +- src/bench/benchmark.cpp | 225 ------------ src/bench/benchmark.h | 222 ------------ src/bench/checkblock.cpp | 4 +- src/bench/coin_selection.cpp | 34 ++ src/bench/spark_bpplus.cpp | 419 +++++++++-------------- src/bench/spark_chaum.cpp | 251 ++++++-------- src/bench/spark_grootle.cpp | 365 +++++++++----------- src/bench/spark_schnorr.cpp | 226 +++++------- 15 files changed, 613 insertions(+), 1278 deletions(-) delete mode 100644 src/bench/benchmark.cpp delete mode 100644 src/bench/benchmark.h 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 4d21c92a92..4e520de846 100644 --- a/cmake/script/GenerateHeaderFromRaw.cmake +++ b/cmake/script/GenerateHeaderFromRaw.cmake @@ -11,6 +11,9 @@ string(REGEX REPLACE "[^\n][^\n]" "std::byte{0x\\0}, " formatted_bytes "${format 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 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 be9931069b..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> diff --git a/src/bench/CMakeLists.txt b/src/bench/CMakeLists.txt index a033881ee7..609b8a16af 100644 --- a/src/bench/CMakeLists.txt +++ b/src/bench/CMakeLists.txt @@ -47,87 +47,12 @@ install(TARGETS bench_firo RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) -# Spark/Lelantus benchmarking framework -option(BUILD_BENCH_SPARK "Build Spark/Lelantus benchmarks" OFF) - if(BUILD_BENCH_SPARK) - message(STATUS "Building Spark benchmarks") - - # Generic benchmarking framework library - add_library(firo_benchmark STATIC - ${CMAKE_CURRENT_SOURCE_DIR}/benchmark.cpp - ) - - target_include_directories(firo_benchmark PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR} - ) - - # Optional: Enable Linux perf counters (requires explicit opt-in) - option(ENABLE_BENCH_PERF_COUNTERS "Enable Linux perf counters in benchmarks (Linux only)" OFF) - - if(ENABLE_BENCH_PERF_COUNTERS) - if(CMAKE_SYSTEM_NAME STREQUAL "Linux") - target_compile_definitions(firo_benchmark PUBLIC BENCH_PERF_COUNTERS) - message(STATUS " Linux perf counters: ENABLED") - else() - message(WARNING " ENABLE_BENCH_PERF_COUNTERS is only supported on Linux, ignoring") - endif() - else() - message(STATUS " Linux perf counters: DISABLED (use -DENABLE_BENCH_PERF_COUNTERS=ON to enable)") - endif() - - # Grootle proof benchmarks - add_executable(bench_spark_grootle - ${CMAKE_CURRENT_SOURCE_DIR}/spark_grootle.cpp - ) - - target_link_libraries(bench_spark_grootle - firo_benchmark - firo_node - Boost::filesystem - secp256k1pp - ) - - # BPPlus (Bulletproofs+) benchmarks - add_executable(bench_spark_bpplus - ${CMAKE_CURRENT_SOURCE_DIR}/spark_bpplus.cpp - ) - - target_link_libraries(bench_spark_bpplus - firo_benchmark - firo_node - Boost::filesystem - secp256k1pp - ) - - # Chaum proof benchmarks - add_executable(bench_spark_chaum - ${CMAKE_CURRENT_SOURCE_DIR}/spark_chaum.cpp - ) - - target_link_libraries(bench_spark_chaum - firo_benchmark - firo_node - Boost::filesystem - secp256k1pp - ) - - # Schnorr signature benchmarks - add_executable(bench_spark_schnorr - ${CMAKE_CURRENT_SOURCE_DIR}/spark_schnorr.cpp - ) - - target_link_libraries(bench_spark_schnorr - firo_benchmark - firo_node - Boost::filesystem - secp256k1pp - ) - - # Install benchmark executables - install(TARGETS bench_spark_grootle bench_spark_bpplus bench_spark_chaum bench_spark_schnorr - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + 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 ) - - message(STATUS " Benchmarks: bench_spark_grootle, bench_spark_bpplus, bench_spark_chaum, bench_spark_schnorr") 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/benchmark.cpp b/src/bench/benchmark.cpp deleted file mode 100644 index d73001bc9d..0000000000 --- a/src/bench/benchmark.cpp +++ /dev/null @@ -1,225 +0,0 @@ -// Copyright (c) 2025 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 "benchmark.h" -#include -#include - -#ifdef __linux__ -#include -#endif - -namespace benchmark { - -// BenchMetrics implementation -void BenchMetrics::print() const { - std::cout << "\n=== Benchmark: " << name << " ===\n"; - std::cout << "Iterations: " << iterations << "\n"; - std::cout << std::fixed << std::setprecision(2); - std::cout << "Time (ns):\n"; - std::cout << " Min: " << min_time_ns << " ns (" << std::setprecision(6) << (min_time_ns / 1e9) << " s)\n"; - std::cout << " Max: " << std::setprecision(2) << max_time_ns << " ns (" << std::setprecision(6) << (max_time_ns / 1e9) << " s)\n"; - std::cout << " Avg: " << std::setprecision(2) << avg_time_ns << " ns (" << std::setprecision(6) << (avg_time_ns / 1e9) << " s)\n"; - std::cout << " Median: " << std::setprecision(2) << median_time_ns << " ns (" << std::setprecision(6) << (median_time_ns / 1e9) << " s)\n"; - std::cout << " StdDev: " << stddev_time_ns << "\n"; - - if (avg_cycles > 0) { - std::cout << "Cycles:\n"; - std::cout << " Min: " << min_cycles << "\n"; - std::cout << " Max: " << max_cycles << "\n"; - std::cout << " Avg: " << avg_cycles << "\n"; - } - -#ifdef BENCH_PERF_COUNTERS - std::cout << "Performance Counters:\n"; - std::cout << " Cache Misses: " << cache_misses << "\n"; - std::cout << " Cache References: " << cache_references << "\n"; - if (cache_references > 0) { - double miss_rate = (double)cache_misses / cache_references * 100.0; - std::cout << " Cache Miss Rate: " << std::setprecision(2) << miss_rate << "%\n"; - } - std::cout << " Branch Misses: " << branch_misses << "\n"; - std::cout << " Instructions: " << instructions << "\n"; - if (avg_cycles > 0 && instructions > 0) { - // IPC = average instructions per iteration / average cycles per iteration - double avg_instructions = (double)instructions / iterations; - double ipc = avg_instructions / avg_cycles; - std::cout << " IPC: " << std::setprecision(2) << ipc << "\n"; - } -#endif - std::cout << std::endl; -} - -void BenchMetrics::print_csv_header() const { - std::cout << "name,iterations,min_ns,max_ns,avg_ns,median_ns,stddev_ns,min_cycles,max_cycles,avg_cycles"; -#ifdef BENCH_PERF_COUNTERS - std::cout << ",cache_misses,cache_refs,branch_misses,instructions"; -#endif - std::cout << "\n"; -} - -void BenchMetrics::print_csv() const { - std::cout << name << "," - << iterations << "," - << std::fixed << std::setprecision(2) - << min_time_ns << "," - << max_time_ns << "," - << avg_time_ns << "," - << median_time_ns << "," - << stddev_time_ns << "," - << min_cycles << "," - << max_cycles << "," - << avg_cycles; -#ifdef BENCH_PERF_COUNTERS - std::cout << "," << cache_misses - << "," << cache_references - << "," << branch_misses - << "," << instructions; -#endif - std::cout << "\n"; -} - -// PerfCounters implementation -#ifdef BENCH_PERF_COUNTERS -int PerfCounters::setup_perf_event(uint32_t type, uint64_t config) { - struct perf_event_attr pe; - memset(&pe, 0, sizeof(struct perf_event_attr)); - pe.type = type; - pe.size = sizeof(struct perf_event_attr); - pe.config = config; - pe.disabled = 1; - pe.exclude_kernel = 1; - pe.exclude_hv = 1; - - int fd = syscall(__NR_perf_event_open, &pe, 0, -1, -1, 0); - return fd; -} -#endif - -PerfCounters::PerfCounters() : cycles(0) { -#ifdef BENCH_PERF_COUNTERS - cache_misses = 0; - cache_references = 0; - branch_misses = 0; - instructions = 0; - - fd_cycles = setup_perf_event(PERF_TYPE_HARDWARE, PERF_COUNT_HW_CPU_CYCLES); - fd_cache_misses = setup_perf_event(PERF_TYPE_HARDWARE, PERF_COUNT_HW_CACHE_MISSES); - fd_cache_refs = setup_perf_event(PERF_TYPE_HARDWARE, PERF_COUNT_HW_CACHE_REFERENCES); - fd_branch_misses = setup_perf_event(PERF_TYPE_HARDWARE, PERF_COUNT_HW_BRANCH_MISSES); - fd_instructions = setup_perf_event(PERF_TYPE_HARDWARE, PERF_COUNT_HW_INSTRUCTIONS); -#endif -} - -PerfCounters::~PerfCounters() { -#ifdef BENCH_PERF_COUNTERS - if (fd_cycles >= 0) close(fd_cycles); - if (fd_cache_misses >= 0) close(fd_cache_misses); - if (fd_cache_refs >= 0) close(fd_cache_refs); - if (fd_branch_misses >= 0) close(fd_branch_misses); - if (fd_instructions >= 0) close(fd_instructions); -#endif -} - -void PerfCounters::start() { -#ifdef BENCH_PERF_COUNTERS - if (fd_cycles >= 0) { - ioctl(fd_cycles, PERF_EVENT_IOC_RESET, 0); - ioctl(fd_cycles, PERF_EVENT_IOC_ENABLE, 0); - } - if (fd_cache_misses >= 0) { - ioctl(fd_cache_misses, PERF_EVENT_IOC_RESET, 0); - ioctl(fd_cache_misses, PERF_EVENT_IOC_ENABLE, 0); - } - if (fd_cache_refs >= 0) { - ioctl(fd_cache_refs, PERF_EVENT_IOC_RESET, 0); - ioctl(fd_cache_refs, PERF_EVENT_IOC_ENABLE, 0); - } - if (fd_branch_misses >= 0) { - ioctl(fd_branch_misses, PERF_EVENT_IOC_RESET, 0); - ioctl(fd_branch_misses, PERF_EVENT_IOC_ENABLE, 0); - } - if (fd_instructions >= 0) { - ioctl(fd_instructions, PERF_EVENT_IOC_RESET, 0); - ioctl(fd_instructions, PERF_EVENT_IOC_ENABLE, 0); - } -#endif -} - -void PerfCounters::stop() { -#ifdef BENCH_PERF_COUNTERS - if (fd_cycles >= 0) { - ioctl(fd_cycles, PERF_EVENT_IOC_DISABLE, 0); - if (read(fd_cycles, &cycles, sizeof(uint64_t)) != static_cast(sizeof(uint64_t))) { - cycles = 0; - } - } - if (fd_cache_misses >= 0) { - ioctl(fd_cache_misses, PERF_EVENT_IOC_DISABLE, 0); - if (read(fd_cache_misses, &cache_misses, sizeof(uint64_t)) != static_cast(sizeof(uint64_t))) { - cache_misses = 0; - } - } - if (fd_cache_refs >= 0) { - ioctl(fd_cache_refs, PERF_EVENT_IOC_DISABLE, 0); - if (read(fd_cache_refs, &cache_references, sizeof(uint64_t)) != static_cast(sizeof(uint64_t))) { - cache_references = 0; - } - } - if (fd_branch_misses >= 0) { - ioctl(fd_branch_misses, PERF_EVENT_IOC_DISABLE, 0); - if (read(fd_branch_misses, &branch_misses, sizeof(uint64_t)) != static_cast(sizeof(uint64_t))) { - branch_misses = 0; - } - } - if (fd_instructions >= 0) { - ioctl(fd_instructions, PERF_EVENT_IOC_DISABLE, 0); - if (read(fd_instructions, &instructions, sizeof(uint64_t)) != static_cast(sizeof(uint64_t))) { - instructions = 0; - } - } -#endif -} - -// BenchRunner implementation -BenchRunner::BenchRunner(const std::string& benchmark_name, uint64_t min_iters, double min_time_secs) - : name(benchmark_name) - , warmup_iterations(3) - , min_iterations(min_iters) - , min_time_seconds(min_time_secs) -{ -} - -void BenchRunner::warmup(std::function func) { - for (uint64_t i = 0; i < warmup_iterations; ++i) { - func(); - } -} - -double BenchRunner::calculate_stddev(const std::vector& values, double mean) const { - if (values.size() <= 1) return 0.0; - - double sum_sq_diff = 0.0; - for (double v : values) { - double diff = v - mean; - sum_sq_diff += diff * diff; - } - - return std::sqrt(sum_sq_diff / (values.size() - 1)); -} - -double BenchRunner::calculate_median(std::vector values) const { - if (values.empty()) return 0.0; - - std::sort(values.begin(), values.end()); - size_t n = values.size(); - - if (n % 2 == 0) { - return (values[n/2 - 1] + values[n/2]) / 2.0; - } else { - return values[n/2]; - } -} - -} // namespace benchmark diff --git a/src/bench/benchmark.h b/src/bench/benchmark.h deleted file mode 100644 index 4b6f9834e1..0000000000 --- a/src/bench/benchmark.h +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright (c) 2025 The Firo Core developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#ifndef FIRO_BENCH_BENCHMARK_H -#define FIRO_BENCH_BENCHMARK_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// Platform-specific performance counter support -// Only enabled if explicitly requested via -DBENCH_PERF_COUNTERS -#ifdef BENCH_PERF_COUNTERS - #ifdef __linux__ - #include - #include - #include - #include - #else - #error "BENCH_PERF_COUNTERS is only supported on Linux" - #endif -#endif - -namespace benchmark { - -// Benchmark metrics structure -struct BenchMetrics { - std::string name; - uint64_t iterations; - - // Timing metrics (always available) - double min_time_ns; - double max_time_ns; - double avg_time_ns; - double median_time_ns; - double stddev_time_ns; - - // CPU cycles (platform-dependent) - uint64_t min_cycles; - uint64_t max_cycles; - uint64_t avg_cycles; - - // Memory metrics (if available) - size_t peak_memory_bytes; - -#ifdef BENCH_PERF_COUNTERS - // Linux perf counters (only if explicitly enabled) - uint64_t cache_misses; - uint64_t cache_references; - uint64_t branch_misses; - uint64_t instructions; -#endif - - void print() const; - void print_csv_header() const; - void print_csv() const; -}; - -// Performance counter wrapper -class PerfCounters { -public: - PerfCounters(); - ~PerfCounters(); - - void start(); - void stop(); - - uint64_t get_cycles() const { return cycles; } - -#ifdef BENCH_PERF_COUNTERS - uint64_t get_cache_misses() const { return cache_misses; } - uint64_t get_cache_references() const { return cache_references; } - uint64_t get_branch_misses() const { return branch_misses; } - uint64_t get_instructions() const { return instructions; } -#endif - -private: - uint64_t cycles; - -#ifdef BENCH_PERF_COUNTERS - int fd_cycles; - int fd_cache_misses; - int fd_cache_refs; - int fd_branch_misses; - int fd_instructions; - - uint64_t cache_misses; - uint64_t cache_references; - uint64_t branch_misses; - uint64_t instructions; - - int setup_perf_event(uint32_t type, uint64_t config); -#endif -}; - -// Benchmark runner -class BenchRunner { -public: - BenchRunner(const std::string& name, uint64_t min_iterations = 10, double min_time_seconds = 1.0); - - // Run benchmark function and collect metrics - template - BenchMetrics run(Func&& func); - - void set_warmup_iterations(uint64_t warmup) { warmup_iterations = warmup; } - void set_min_iterations(uint64_t min_iter) { min_iterations = min_iter; } - void set_min_time(double seconds) { min_time_seconds = seconds; } - -private: - std::string name; - uint64_t warmup_iterations; - uint64_t min_iterations; - double min_time_seconds; - - void warmup(std::function func); - double calculate_stddev(const std::vector& values, double mean) const; - double calculate_median(std::vector values) const; -}; - -// Template implementation -template -BenchMetrics BenchRunner::run(Func&& func) { - BenchMetrics metrics = {}; - metrics.name = name; - - // Warmup phase - std::function func_wrapper = func; - warmup(func_wrapper); - - // Measurement phase - std::vector times; - std::vector cycles_vec; - times.reserve(min_iterations * 2); - cycles_vec.reserve(min_iterations * 2); - -#ifdef BENCH_PERF_COUNTERS - uint64_t total_cache_misses = 0; - uint64_t total_cache_refs = 0; - uint64_t total_branch_misses = 0; - uint64_t total_instructions = 0; -#endif - - uint64_t total_iterations = 0; - auto start_time = std::chrono::steady_clock::now(); - - PerfCounters perf; - - while (total_iterations < min_iterations || - std::chrono::duration(std::chrono::steady_clock::now() - start_time).count() < min_time_seconds) { - - perf.start(); - auto t1 = std::chrono::high_resolution_clock::now(); - - func(); - - auto t2 = std::chrono::high_resolution_clock::now(); - perf.stop(); - - double elapsed_ns = std::chrono::duration(t2 - t1).count(); - times.push_back(elapsed_ns); - cycles_vec.push_back(perf.get_cycles()); - -#ifdef BENCH_PERF_COUNTERS - total_cache_misses += perf.get_cache_misses(); - total_cache_refs += perf.get_cache_references(); - total_branch_misses += perf.get_branch_misses(); - total_instructions += perf.get_instructions(); -#endif - - total_iterations++; - } - - // Calculate statistics - metrics.iterations = total_iterations; - - metrics.min_time_ns = *std::min_element(times.begin(), times.end()); - metrics.max_time_ns = *std::max_element(times.begin(), times.end()); - - double sum = 0.0; - for (double t : times) sum += t; - metrics.avg_time_ns = sum / times.size(); - - metrics.median_time_ns = calculate_median(times); - metrics.stddev_time_ns = calculate_stddev(times, metrics.avg_time_ns); - - if (!cycles_vec.empty()) { - metrics.min_cycles = *std::min_element(cycles_vec.begin(), cycles_vec.end()); - metrics.max_cycles = *std::max_element(cycles_vec.begin(), cycles_vec.end()); - - uint64_t sum_cycles = 0; - for (uint64_t c : cycles_vec) sum_cycles += c; - metrics.avg_cycles = sum_cycles / cycles_vec.size(); - } - -#ifdef BENCH_PERF_COUNTERS - metrics.cache_misses = total_cache_misses; - metrics.cache_references = total_cache_refs; - metrics.branch_misses = total_branch_misses; - metrics.instructions = total_instructions; -#endif - - return metrics; -} - -// Macro for easy benchmark registration -#define BENCHMARK(name, func) \ - static void bench_##name() { \ - benchmark::BenchRunner runner(#name); \ - auto metrics = runner.run(func); \ - metrics.print(); \ - } - -} // namespace benchmark - -#endif // FIRO_BENCH_BENCHMARK_H diff --git a/src/bench/checkblock.cpp b/src/bench/checkblock.cpp index 694f17e543..a90535657f 100644 --- a/src/bench/checkblock.cpp +++ b/src/bench/checkblock.cpp @@ -47,8 +47,10 @@ static void DeserializeAndCheckBlockTest(benchmark::State& state) 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/spark_bpplus.cpp b/src/bench/spark_bpplus.cpp index 221d8c8ea8..59d8a813e6 100644 --- a/src/bench/spark_bpplus.cpp +++ b/src/bench/spark_bpplus.cpp @@ -1,298 +1,215 @@ -// Copyright (c) 2025 The Firo Core developers +// 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 "benchmark.h" +#include "bench.h" + #include "../libspark/bpplus.h" #include "../libspark/params.h" -#include -using namespace spark; -using namespace benchmark; +#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(); + } +} -// Helper to generate test data for BPPlus range proofs struct BPPlusTestData { - std::size_t N; // bit length + std::size_t N; std::size_t num_outputs; - - GroupElement G, H; - std::vector Gi, Hi; - - std::vector v; // values - std::vector r; // randomness - std::vector C; // commitments - - BPPlusTestData(std::size_t N_, std::size_t num_outputs_) : N(N_), num_outputs(num_outputs_) { - // Generate generators - G.randomize(); - H.randomize(); - - // Pad num_outputs to next power of 2 if needed - std::size_t M = num_outputs; - if ((M & (M - 1)) != 0) { - M = 1 << (64 - __builtin_clzll(M)); - } - - Gi.resize(N * M); - Hi.resize(N * M); - for (std::size_t i = 0; i < N * M; ++i) { - Gi[i].randomize(); - Hi[i].randomize(); + + 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"); } - - // Generate values and commitments + 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) { - // Generate random value within range - uint64_t val = rand() % (1ULL << std::min(N, 32UL)); - v[i] = Scalar(val); + v[i] = Scalar(static_cast(i + 1)); r[i].randomize(); - - // Commitment: C = vG + rH C[i] = G * v[i] + H * r[i]; } } }; -// Benchmark BPPlus proof generation with different output counts -void bench_bpplus_prove_1output() { - BPPlusTestData data(64, 1); - BPPlus bpplus(data.G, data.H, data.Gi, data.Hi, data.N); - - BenchRunner runner("BPPlus_Prove_1Output_64bit", 10, 1.0); - auto metrics = runner.run([&]() { - BPPlusProof proof; +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); - }); - metrics.print(); + } } -void bench_bpplus_prove_2outputs() { - BPPlusTestData data(64, 2); - BPPlus bpplus(data.G, data.H, data.Gi, data.Hi, data.N); - - BenchRunner runner("BPPlus_Prove_2Outputs_64bit", 10, 1.0); - auto metrics = runner.run([&]() { - BPPlusProof proof; - bpplus.prove(data.v, data.r, data.C, proof); - }); - metrics.print(); +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 bench_bpplus_prove_4outputs() { - BPPlusTestData data(64, 4); - BPPlus bpplus(data.G, data.H, data.Gi, data.Hi, data.N); - - BenchRunner runner("BPPlus_Prove_4Outputs_64bit", 10, 1.0); - auto metrics = runner.run([&]() { - BPPlusProof proof; - bpplus.prove(data.v, data.r, data.C, proof); - }); - metrics.print(); +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 bench_bpplus_prove_8outputs() { - BPPlusTestData data(64, 8); - BPPlus bpplus(data.G, data.H, data.Gi, data.Hi, data.N); - - BenchRunner runner("BPPlus_Prove_8Outputs_64bit", 5, 1.0); - auto metrics = runner.run([&]() { - BPPlusProof proof; - bpplus.prove(data.v, data.r, data.C, proof); - }); - metrics.print(); +void SparkBPPlusProve1Output64Bit(benchmark::State& state) +{ + BPPlusProve(state, 64, 1); } -// Benchmark with different bit lengths -void bench_bpplus_prove_32bit() { - BPPlusTestData data(32, 2); - BPPlus bpplus(data.G, data.H, data.Gi, data.Hi, data.N); - - BenchRunner runner("BPPlus_Prove_2Outputs_32bit", 10, 1.0); - auto metrics = runner.run([&]() { - BPPlusProof proof; - bpplus.prove(data.v, data.r, data.C, proof); - }); - metrics.print(); +void SparkBPPlusProve2Outputs64Bit(benchmark::State& state) +{ + BPPlusProve(state, 64, 2); } -void bench_bpplus_prove_128bit() { - BPPlusTestData data(128, 2); - BPPlus bpplus(data.G, data.H, data.Gi, data.Hi, data.N); - - BenchRunner runner("BPPlus_Prove_2Outputs_128bit", 5, 1.0); - auto metrics = runner.run([&]() { - BPPlusProof proof; - bpplus.prove(data.v, data.r, data.C, proof); - }); - metrics.print(); +void SparkBPPlusProve4Outputs64Bit(benchmark::State& state) +{ + BPPlusProve(state, 64, 4); } -// Benchmark BPPlus proof verification -void bench_bpplus_verify_1output() { - BPPlusTestData data(64, 1); - BPPlus bpplus(data.G, data.H, data.Gi, data.Hi, data.N); - - BPPlusProof proof; - bpplus.prove(data.v, data.r, data.C, proof); - - BenchRunner runner("BPPlus_Verify_1Output_64bit", 50, 1.0); - auto metrics = runner.run([&]() { - bool result = bpplus.verify(data.C, proof); - if (!result) { - std::cerr << "Verification failed!\n"; - } - }); - metrics.print(); +void SparkBPPlusProve8Outputs64Bit(benchmark::State& state) +{ + BPPlusProve(state, 64, 8); } -void bench_bpplus_verify_2outputs() { - BPPlusTestData data(64, 2); - BPPlus bpplus(data.G, data.H, data.Gi, data.Hi, data.N); - - BPPlusProof proof; - bpplus.prove(data.v, data.r, data.C, proof); - - BenchRunner runner("BPPlus_Verify_2Outputs_64bit", 50, 1.0); - auto metrics = runner.run([&]() { - bool result = bpplus.verify(data.C, proof); - if (!result) { - std::cerr << "Verification failed!\n"; - } - }); - metrics.print(); +void SparkBPPlusProve2Outputs32Bit(benchmark::State& state) +{ + BPPlusProve(state, 32, 2); } -void bench_bpplus_verify_4outputs() { - BPPlusTestData data(64, 4); - BPPlus bpplus(data.G, data.H, data.Gi, data.Hi, data.N); - - BPPlusProof proof; - bpplus.prove(data.v, data.r, data.C, proof); - - BenchRunner runner("BPPlus_Verify_4Outputs_64bit", 50, 1.0); - auto metrics = runner.run([&]() { - bool result = bpplus.verify(data.C, proof); - if (!result) { - std::cerr << "Verification failed!\n"; - } - }); - metrics.print(); +void SparkBPPlusProve2Outputs128Bit(benchmark::State& state) +{ + BPPlusProve(state, 128, 2); } -void bench_bpplus_verify_8outputs() { - BPPlusTestData data(64, 8); - BPPlus bpplus(data.G, data.H, data.Gi, data.Hi, data.N); - - BPPlusProof proof; - bpplus.prove(data.v, data.r, data.C, proof); - - BenchRunner runner("BPPlus_Verify_8Outputs_64bit", 30, 1.0); - auto metrics = runner.run([&]() { - bool result = bpplus.verify(data.C, proof); - if (!result) { - std::cerr << "Verification failed!\n"; - } - }); - metrics.print(); +void SparkBPPlusVerify1Output64Bit(benchmark::State& state) +{ + BPPlusVerify(state, 64, 1); } -// Benchmark batch verification -void bench_bpplus_batch_verify_10proofs() { - const size_t num_proofs = 10; - BPPlusTestData data(64, 2); - BPPlus bpplus(data.G, data.H, data.Gi, data.Hi, data.N); - - // Generate multiple proofs - std::vector proofs; - std::vector> C_vec; - - for (size_t i = 0; i < num_proofs; ++i) { - // Generate independent values and commitments for each proof - std::vector v_i(2), r_i(2); - std::vector C_i(2); - for (size_t j = 0; j < 2; ++j) { - uint64_t val = rand() % (1ULL << 32); - v_i[j] = Scalar(val); - r_i[j].randomize(); - C_i[j] = data.G * v_i[j] + data.H * r_i[j]; - } - BPPlusProof proof; - bpplus.prove(v_i, r_i, C_i, proof); - proofs.push_back(proof); - C_vec.push_back(C_i); - } +void SparkBPPlusVerify2Outputs64Bit(benchmark::State& state) +{ + BPPlusVerify(state, 64, 2); +} - BenchRunner runner("BPPlus_BatchVerify_10Proofs_2Outputs", 20, 1.0); - auto metrics = runner.run([&]() { - bool result = bpplus.verify(C_vec, proofs); - if (!result) { - std::cerr << "Batch verification failed!\n"; - } - }); - metrics.print(); +void SparkBPPlusVerify4Outputs64Bit(benchmark::State& state) +{ + BPPlusVerify(state, 64, 4); } -void bench_bpplus_batch_verify_50proofs() { - const size_t num_proofs = 50; - BPPlusTestData data(64, 2); - BPPlus bpplus(data.G, data.H, data.Gi, data.Hi, data.N); - - // Generate multiple proofs - std::vector proofs; - std::vector> C_vec; - - for (size_t i = 0; i < num_proofs; ++i) { - // Generate independent values and commitments for each proof - std::vector v_i(2), r_i(2); - std::vector C_i(2); - for (size_t j = 0; j < 2; ++j) { - uint64_t val = rand() % (1ULL << 32); - v_i[j] = Scalar(val); - r_i[j].randomize(); - C_i[j] = data.G * v_i[j] + data.H * r_i[j]; - } - BPPlusProof proof; - bpplus.prove(v_i, r_i, C_i, proof); - proofs.push_back(proof); - C_vec.push_back(C_i); - } - - BenchRunner runner("BPPlus_BatchVerify_50Proofs_2Outputs", 10, 1.0); - auto metrics = runner.run([&]() { - bool result = bpplus.verify(C_vec, proofs); - if (!result) { - std::cerr << "Batch verification failed!\n"; - } - }); - metrics.print(); +void SparkBPPlusVerify8Outputs64Bit(benchmark::State& state) +{ + BPPlusVerify(state, 64, 8); } -int main() { - std::cout << "=== Spark BPPlus (Bulletproofs+) Benchmarks ===\n\n"; - - std::cout << "--- Proof Generation (varying output count) ---\n"; - bench_bpplus_prove_1output(); - bench_bpplus_prove_2outputs(); - bench_bpplus_prove_4outputs(); - bench_bpplus_prove_8outputs(); - - std::cout << "\n--- Proof Generation (varying bit length) ---\n"; - bench_bpplus_prove_32bit(); - bench_bpplus_prove_128bit(); - - std::cout << "\n--- Proof Verification ---\n"; - bench_bpplus_verify_1output(); - bench_bpplus_verify_2outputs(); - bench_bpplus_verify_4outputs(); - bench_bpplus_verify_8outputs(); - - std::cout << "\n--- Batch Verification ---\n"; - bench_bpplus_batch_verify_10proofs(); - bench_bpplus_batch_verify_50proofs(); - - return 0; +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 index df5def83b1..a8c13db1bc 100644 --- a/src/bench/spark_chaum.cpp +++ b/src/bench/spark_chaum.cpp @@ -1,190 +1,141 @@ -// Copyright (c) 2025 The Firo Core developers +// 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 "benchmark.h" +#include "bench.h" + #include "../libspark/chaum.h" -#include +#include "../libspark/params.h" + +#include +#include +#include + +namespace { -using namespace spark; -using namespace benchmark; +using secp_primitives::GroupElement; +using secp_primitives::Scalar; + +void RequireValid(const bool result) +{ + if (!result) { + std::abort(); + } +} -// Helper to generate test data for Chaum proofs struct ChaumTestData { - std::size_t n; // number of commitments - - GroupElement F, G, H, U; + std::size_t n; + + GroupElement F; + GroupElement G; + GroupElement H; + GroupElement U; Scalar mu; - - std::vector x, y, z; - std::vector S, T; - - ChaumTestData(std::size_t n_) : n(n_) { - // Generate generators - F.randomize(); - G.randomize(); - H.randomize(); - U.randomize(); - - // Generate 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(); - - // Generate witness scalars + 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(); - } - - // Generate commitments S and T - // S[i] = F^x[i] * G^y[i] * H^z[i] - // T[i] = U^x[i] * G^(mu*y[i]) - S.resize(n); - T.resize(n); - - for (std::size_t i = 0; i < n; ++i) { + S[i] = F * x[i] + G * y[i] + H * z[i]; T[i] = (U + G * y[i].negate()) * x[i].inverse(); } } }; -// Benchmark Chaum proof generation with different commitment counts -void bench_chaum_prove_1commitment() { - ChaumTestData data(1); - Chaum chaum(data.F, data.G, data.H, data.U); - - BenchRunner runner("Chaum_Prove_1Commitment", 100, 1.0); - auto metrics = runner.run([&]() { - ChaumProof proof; +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); - }); - metrics.print(); + } } -void bench_chaum_prove_2commitments() { - ChaumTestData data(2); - Chaum chaum(data.F, data.G, data.H, data.U); - - BenchRunner runner("Chaum_Prove_2Commitments", 100, 1.0); - auto metrics = runner.run([&]() { - ChaumProof proof; - chaum.prove(data.mu, data.x, data.y, data.z, data.S, data.T, proof); - }); - metrics.print(); +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 bench_chaum_prove_4commitments() { - ChaumTestData data(4); - Chaum chaum(data.F, data.G, data.H, data.U); - - BenchRunner runner("Chaum_Prove_4Commitments", 100, 1.0); - auto metrics = runner.run([&]() { - ChaumProof proof; - chaum.prove(data.mu, data.x, data.y, data.z, data.S, data.T, proof); - }); - metrics.print(); +void SparkChaumProve1Commitment(benchmark::State& state) +{ + ChaumProve(state, 1); } -void bench_chaum_prove_8commitments() { - ChaumTestData data(8); - Chaum chaum(data.F, data.G, data.H, data.U); - - BenchRunner runner("Chaum_Prove_8Commitments", 50, 1.0); - auto metrics = runner.run([&]() { - ChaumProof proof; - chaum.prove(data.mu, data.x, data.y, data.z, data.S, data.T, proof); - }); - metrics.print(); +void SparkChaumProve2Commitments(benchmark::State& state) +{ + ChaumProve(state, 2); } -// Benchmark Chaum proof verification -void bench_chaum_verify_1commitment() { - ChaumTestData data(1); - Chaum chaum(data.F, data.G, data.H, data.U); - - ChaumProof proof; - chaum.prove(data.mu, data.x, data.y, data.z, data.S, data.T, proof); - - BenchRunner runner("Chaum_Verify_1Commitment", 500, 1.0); - auto metrics = runner.run([&]() { - bool result = chaum.verify(data.mu, data.S, data.T, proof); - if (!result) { - std::cerr << "Verification failed!\n"; - } - }); - metrics.print(); +void SparkChaumProve4Commitments(benchmark::State& state) +{ + ChaumProve(state, 4); } -void bench_chaum_verify_2commitments() { - ChaumTestData data(2); - Chaum chaum(data.F, data.G, data.H, data.U); - - ChaumProof proof; - chaum.prove(data.mu, data.x, data.y, data.z, data.S, data.T, proof); - - BenchRunner runner("Chaum_Verify_2Commitments", 500, 1.0); - auto metrics = runner.run([&]() { - bool result = chaum.verify(data.mu, data.S, data.T, proof); - if (!result) { - std::cerr << "Verification failed!\n"; - } - }); - metrics.print(); +void SparkChaumProve8Commitments(benchmark::State& state) +{ + ChaumProve(state, 8); } -void bench_chaum_verify_4commitments() { - ChaumTestData data(4); - Chaum chaum(data.F, data.G, data.H, data.U); - - ChaumProof proof; - chaum.prove(data.mu, data.x, data.y, data.z, data.S, data.T, proof); - - BenchRunner runner("Chaum_Verify_4Commitments", 500, 1.0); - auto metrics = runner.run([&]() { - bool result = chaum.verify(data.mu, data.S, data.T, proof); - if (!result) { - std::cerr << "Verification failed!\n"; - } - }); - metrics.print(); +void SparkChaumVerify1Commitment(benchmark::State& state) +{ + ChaumVerify(state, 1); } -void bench_chaum_verify_8commitments() { - ChaumTestData data(8); - Chaum chaum(data.F, data.G, data.H, data.U); - - ChaumProof proof; - chaum.prove(data.mu, data.x, data.y, data.z, data.S, data.T, proof); - - BenchRunner runner("Chaum_Verify_8Commitments", 500, 1.0); - auto metrics = runner.run([&]() { - bool result = chaum.verify(data.mu, data.S, data.T, proof); - if (!result) { - std::cerr << "Verification failed!\n"; - } - }); - metrics.print(); +void SparkChaumVerify2Commitments(benchmark::State& state) +{ + ChaumVerify(state, 2); } -int main() { - std::cout << "=== Spark Chaum Proof Benchmarks ===\n\n"; - - std::cout << "--- Proof Generation ---\n"; - bench_chaum_prove_1commitment(); - bench_chaum_prove_2commitments(); - bench_chaum_prove_4commitments(); - bench_chaum_prove_8commitments(); - - std::cout << "\n--- Proof Verification ---\n"; - bench_chaum_verify_1commitment(); - bench_chaum_verify_2commitments(); - bench_chaum_verify_4commitments(); - bench_chaum_verify_8commitments(); - - return 0; +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 index fd243fb77c..749b9e58cf 100644 --- a/src/bench/spark_grootle.cpp +++ b/src/bench/spark_grootle.cpp @@ -1,267 +1,224 @@ -// Copyright (c) 2025 The Firo Core developers +// 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 "benchmark.h" +#include "bench.h" + #include "../libspark/grootle.h" -#include "../libspark/params.h" #include "../random.h" -#include -using namespace spark; -using namespace benchmark; +#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; +} -// Helper to generate test data for Grootle proofs struct GrootleTestData { std::size_t n; std::size_t m; std::size_t set_size; - std::size_t l; // hidden index - + 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(std::size_t n_, std::size_t m_) : n(n_), m(m_) { - set_size = 1; - for (std::size_t i = 0; i < m; ++i) { - set_size *= n; - } - - // Generate random hidden index - l = rand() % set_size; - - // Generate generators + + 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(); } - - // Generate witness + s.randomize(); v.randomize(); - - // Generate sets S and V + S.resize(set_size); V.resize(set_size); for (std::size_t i = 0; i < set_size; ++i) { S[i].randomize(); V[i].randomize(); } - - // Set the actual values at hidden index + S1 = S[l] + H * s.negate(); V1 = V[l] + H * v.negate(); - - // Generate random root - root.resize(32); - GetRandBytes(root.data(), 32); + + 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); + } } }; -// Benchmark Grootle proof generation with different set sizes -void bench_grootle_prove_n4_m2() { - GrootleTestData data(4, 2); // set size = 16 - Grootle grootle(data.H, data.Gi, data.Hi, data.n, data.m); - - BenchRunner runner("Grootle_Prove_N4_M2_SetSize16", 10, 1.0); - auto metrics = runner.run([&]() { - GrootleProof proof; +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); - }); - metrics.print(); + } } -void bench_grootle_prove_n4_m3() { - GrootleTestData data(4, 3); // set size = 64 - Grootle grootle(data.H, data.Gi, data.Hi, data.n, data.m); - - BenchRunner runner("Grootle_Prove_N4_M3_SetSize64", 10, 1.0); - auto metrics = runner.run([&]() { - GrootleProof proof; - grootle.prove(data.l, data.s, data.S, data.S1, data.v, data.V, data.V1, data.root, proof); - }); - metrics.print(); +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 bench_grootle_prove_n4_m4() { - GrootleTestData data(4, 4); // set size = 256 - Grootle grootle(data.H, data.Gi, data.Hi, data.n, data.m); - - BenchRunner runner("Grootle_Prove_N4_M4_SetSize256", 10, 1.0); - auto metrics = runner.run([&]() { - GrootleProof proof; - grootle.prove(data.l, data.s, data.S, data.S1, data.v, data.V, data.V1, data.root, proof); - }); - metrics.print(); +void SparkGrootleProveN4M2(benchmark::State& state) +{ + GrootleProve(state, 4, 2); } -void bench_grootle_prove_n8_m3() { - GrootleTestData data(8, 3); // set size = 512 - Grootle grootle(data.H, data.Gi, data.Hi, data.n, data.m); - - BenchRunner runner("Grootle_Prove_N8_M3_SetSize512", 5, 1.0); - auto metrics = runner.run([&]() { - GrootleProof proof; - grootle.prove(data.l, data.s, data.S, data.S1, data.v, data.V, data.V1, data.root, proof); - }); - metrics.print(); +void SparkGrootleProveN4M3(benchmark::State& state) +{ + GrootleProve(state, 4, 3); } -void bench_grootle_prove_n16_m3() { - GrootleTestData data(16, 3); // set size = 4096 - Grootle grootle(data.H, data.Gi, data.Hi, data.n, data.m); - - BenchRunner runner("Grootle_Prove_N16_M3_SetSize4096", 3, 1.0); - auto metrics = runner.run([&]() { - GrootleProof proof; - grootle.prove(data.l, data.s, data.S, data.S1, data.v, data.V, data.V1, data.root, proof); - }); - metrics.print(); +void SparkGrootleProveN4M4(benchmark::State& state) +{ + GrootleProve(state, 4, 4); } -// Benchmark Grootle proof verification -void bench_grootle_verify_n4_m2() { - GrootleTestData data(4, 2); - Grootle grootle(data.H, data.Gi, data.Hi, data.n, data.m); - - GrootleProof proof; - grootle.prove(data.l, data.s, data.S, data.S1, data.v, data.V, data.V1, data.root, proof); - - BenchRunner runner("Grootle_Verify_N4_M2_SetSize16", 50, 1.0); - auto metrics = runner.run([&]() { - bool result = grootle.verify(data.S, data.S1, data.V, data.V1, data.root, data.set_size, proof); - if (!result) { - std::cerr << "Verification failed!\n"; - } - }); - metrics.print(); +void SparkGrootleProveN8M3(benchmark::State& state) +{ + GrootleProve(state, 8, 3); } -void bench_grootle_verify_n4_m3() { - GrootleTestData data(4, 3); - Grootle grootle(data.H, data.Gi, data.Hi, data.n, data.m); - - GrootleProof proof; - grootle.prove(data.l, data.s, data.S, data.S1, data.v, data.V, data.V1, data.root, proof); - - BenchRunner runner("Grootle_Verify_N4_M3_SetSize64", 50, 1.0); - auto metrics = runner.run([&]() { - bool result = grootle.verify(data.S, data.S1, data.V, data.V1, data.root, data.set_size, proof); - if (!result) { - std::cerr << "Verification failed!\n"; - } - }); - metrics.print(); +void SparkGrootleProveN16M3(benchmark::State& state) +{ + GrootleProve(state, 16, 3); } -void bench_grootle_verify_n4_m4() { - GrootleTestData data(4, 4); - Grootle grootle(data.H, data.Gi, data.Hi, data.n, data.m); - - GrootleProof proof; - grootle.prove(data.l, data.s, data.S, data.S1, data.v, data.V, data.V1, data.root, proof); - - BenchRunner runner("Grootle_Verify_N4_M4_SetSize256", 50, 1.0); - auto metrics = runner.run([&]() { - bool result = grootle.verify(data.S, data.S1, data.V, data.V1, data.root, data.set_size, proof); - if (!result) { - std::cerr << "Verification failed!\n"; - } - }); - metrics.print(); +void SparkGrootleVerifyN4M2(benchmark::State& state) +{ + GrootleVerify(state, 4, 2); } -void bench_grootle_verify_n16_m3() { - GrootleTestData data(16, 3); - Grootle grootle(data.H, data.Gi, data.Hi, data.n, data.m); - - GrootleProof proof; - grootle.prove(data.l, data.s, data.S, data.S1, data.v, data.V, data.V1, data.root, proof); - - BenchRunner runner("Grootle_Verify_N16_M3_SetSize4096", 20, 1.0); - auto metrics = runner.run([&]() { - bool result = grootle.verify(data.S, data.S1, data.V, data.V1, data.root, data.set_size, proof); - if (!result) { - std::cerr << "Verification failed!\n"; - } - }); - metrics.print(); +void SparkGrootleVerifyN4M3(benchmark::State& state) +{ + GrootleVerify(state, 4, 3); } -// Benchmark batch verification -void bench_grootle_batch_verify_10proofs() { - const size_t num_proofs = 10; - GrootleTestData data(4, 3); // set size = 64 - Grootle grootle(data.H, data.Gi, data.Hi, data.n, data.m); - - // Generate multiple proofs - std::vector proofs; - std::vector S1_vec; - std::vector V1_vec; - std::vector> roots; - std::vector sizes; - - for (size_t i = 0; i < num_proofs; ++i) { - // Generate independent witness for each proof, but use same anonymity set - size_t l = rand() % data.set_size; - Scalar s, v; - s.randomize(); - v.randomize(); - - GroupElement S1 = data.S[l] + data.H * s.negate(); - GroupElement V1 = data.V[l] + data.H * v.negate(); - - GrootleProof proof; - grootle.prove(l, s, data.S, S1, v, data.V, V1, data.root, proof); - proofs.push_back(proof); - S1_vec.push_back(S1); - V1_vec.push_back(V1); - roots.push_back(data.root); - sizes.push_back(data.set_size); - } - - BenchRunner runner("Grootle_BatchVerify_10Proofs_SetSize64", 20, 1.0); - auto metrics = runner.run([&]() { - bool result = grootle.verify(data.S, S1_vec, data.V, V1_vec, roots, sizes, proofs); - if (!result) { - std::cerr << "Batch verification failed!\n"; - } - }); - metrics.print(); +void SparkGrootleVerifyN4M4(benchmark::State& state) +{ + GrootleVerify(state, 4, 4); } -int main() { - std::cout << "=== Spark Grootle Proof Benchmarks ===\n\n"; - - std::cout << "--- Proof Generation ---\n"; - bench_grootle_prove_n4_m2(); - bench_grootle_prove_n4_m3(); - bench_grootle_prove_n4_m4(); - bench_grootle_prove_n8_m3(); - bench_grootle_prove_n16_m3(); - - std::cout << "\n--- Proof Verification ---\n"; - bench_grootle_verify_n4_m2(); - bench_grootle_verify_n4_m3(); - bench_grootle_verify_n4_m4(); - bench_grootle_verify_n16_m3(); - - std::cout << "\n--- Batch Verification ---\n"; - bench_grootle_batch_verify_10proofs(); - - return 0; +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 index 64c505d629..e925bc77a3 100644 --- a/src/bench/spark_schnorr.cpp +++ b/src/bench/spark_schnorr.cpp @@ -1,30 +1,36 @@ -// Copyright (c) 2025 The Firo Core developers +// 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 "benchmark.h" +#include "bench.h" + +#include "../libspark/params.h" #include "../libspark/schnorr.h" -#include -using namespace spark; -using namespace benchmark; +#include +#include +#include + +namespace { + +using secp_primitives::GroupElement; +using secp_primitives::Scalar; -// Helper to generate test data for Schnorr signatures struct SchnorrTestData { std::size_t n; // number of keys - + GroupElement G; std::vector y; std::vector Y; - - SchnorrTestData(std::size_t n_) : n(n_) { - // Generate generator - G.randomize(); - - // Generate key pairs + + 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]; @@ -32,140 +38,94 @@ struct SchnorrTestData { } }; -// Benchmark Schnorr signature generation (single key) -void bench_schnorr_prove_1key() { - SchnorrTestData data(1); - Schnorr schnorr(data.G); - - BenchRunner runner("Schnorr_Sign_1Key", 500, 1.0); - auto metrics = runner.run([&]() { - SchnorrProof proof; - schnorr.prove(data.y[0], data.Y[0], proof); - }); - metrics.print(); +void RequireValid(const bool result) +{ + if (!result) { + std::abort(); + } } -// Benchmark Schnorr signature generation (multiple keys) -void bench_schnorr_prove_2keys() { - SchnorrTestData data(2); - Schnorr schnorr(data.G); - - BenchRunner runner("Schnorr_Sign_2Keys", 500, 1.0); - auto metrics = runner.run([&]() { - SchnorrProof proof; - schnorr.prove(data.y, data.Y, proof); - }); - metrics.print(); +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 bench_schnorr_prove_4keys() { - SchnorrTestData data(4); - Schnorr schnorr(data.G); - - BenchRunner runner("Schnorr_Sign_4Keys", 500, 1.0); - auto metrics = runner.run([&]() { - SchnorrProof 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); - }); - metrics.print(); + while (state.KeepRunning()) { + RequireValid(schnorr.verify(data.Y, proof)); + } + } } -void bench_schnorr_prove_8keys() { - SchnorrTestData data(8); - Schnorr schnorr(data.G); - - BenchRunner runner("Schnorr_Sign_8Keys", 500, 1.0); - auto metrics = runner.run([&]() { - SchnorrProof proof; - schnorr.prove(data.y, data.Y, proof); - }); - metrics.print(); +void SparkSchnorrProve1Key(benchmark::State& state) +{ + SchnorrProveKeys(state, 1); } -// Benchmark Schnorr signature verification (single key) -void bench_schnorr_verify_1key() { - SchnorrTestData data(1); - Schnorr schnorr(data.G); - - SchnorrProof proof; - schnorr.prove(data.y[0], data.Y[0], proof); - - BenchRunner runner("Schnorr_Verify_1Key", 1000, 1.0); - auto metrics = runner.run([&]() { - bool result = schnorr.verify(data.Y[0], proof); - if (!result) { - std::cerr << "Verification failed!\n"; - } - }); - metrics.print(); +void SparkSchnorrProve2Keys(benchmark::State& state) +{ + SchnorrProveKeys(state, 2); } -// Benchmark Schnorr signature verification (multiple keys) -void bench_schnorr_verify_2keys() { - SchnorrTestData data(2); - Schnorr schnorr(data.G); - - SchnorrProof proof; - schnorr.prove(data.y, data.Y, proof); - - BenchRunner runner("Schnorr_Verify_2Keys", 1000, 1.0); - auto metrics = runner.run([&]() { - bool result = schnorr.verify(data.Y, proof); - if (!result) { - std::cerr << "Verification failed!\n"; - } - }); - metrics.print(); +void SparkSchnorrProve4Keys(benchmark::State& state) +{ + SchnorrProveKeys(state, 4); } -void bench_schnorr_verify_4keys() { - SchnorrTestData data(4); - Schnorr schnorr(data.G); - - SchnorrProof proof; - schnorr.prove(data.y, data.Y, proof); - - BenchRunner runner("Schnorr_Verify_4Keys", 1000, 1.0); - auto metrics = runner.run([&]() { - bool result = schnorr.verify(data.Y, proof); - if (!result) { - std::cerr << "Verification failed!\n"; - } - }); - metrics.print(); +void SparkSchnorrProve8Keys(benchmark::State& state) +{ + SchnorrProveKeys(state, 8); } -void bench_schnorr_verify_8keys() { - SchnorrTestData data(8); - Schnorr schnorr(data.G); - - SchnorrProof proof; - schnorr.prove(data.y, data.Y, proof); - - BenchRunner runner("Schnorr_Verify_8Keys", 1000, 1.0); - auto metrics = runner.run([&]() { - bool result = schnorr.verify(data.Y, proof); - if (!result) { - std::cerr << "Verification failed!\n"; - } - }); - metrics.print(); +void SparkSchnorrVerify1Key(benchmark::State& state) +{ + SchnorrVerifyKeys(state, 1); +} + +void SparkSchnorrVerify2Keys(benchmark::State& state) +{ + SchnorrVerifyKeys(state, 2); } -int main() { - std::cout << "=== Spark Schnorr Signature Benchmarks ===\n\n"; - - std::cout << "--- Signature Generation ---\n"; - bench_schnorr_prove_1key(); - bench_schnorr_prove_2keys(); - bench_schnorr_prove_4keys(); - bench_schnorr_prove_8keys(); - - std::cout << "\n--- Signature Verification ---\n"; - bench_schnorr_verify_1key(); - bench_schnorr_verify_2keys(); - bench_schnorr_verify_4keys(); - bench_schnorr_verify_8keys(); - - return 0; +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);