UltrafastSecp256k1/cpu/tests/test_hash_accel.cpp
Vano Chkheidze bb1967944b
fix: suppress ~550 code scanning alerts (batch 2) (#56)
Categories fixed:
- objectIndex (64): cppcheck-suppress in sha512.hpp
- cert-err33-c (159): (void) cast on printf/snprintf/fprintf return values
- cert-msc32-c (44): NOLINT on intentional constant-seed mt19937 in tests
- bugprone-implicit-widening (43): static_cast<size_t> on loop counters
- misc-const-correctness (37): add const to variables where safe
- shiftTooManyBitsSigned (16): suppress intentional sign-bit arithmetic shifts
- StackAddressEscape: NOLINTBEGIN in bench_field_26.cpp (benchmark escape idiom)
- nullPointerRedundantCheck (14): suppress defensive null checks
- arrayIndexOutOfBoundsCond (6): suppress bounded-loop false positives
- containerOutOfBounds (2): suppress guarded access false positive
- uninitMemberVar (2): init buf_ in RIPEMD160 constructors
- passedByValue (1): pass FieldElement by const ref (field.cpp/field.hpp)
- AssignmentAddressToInteger (1): fix pointer-to-integer in test_coins.cpp

Build: 48/48 targets OK (MSVC/Clang)
Tests: 25/25 passed
2026-02-28 00:50:00 +04:00

450 lines
16 KiB
C++

// ============================================================================
// Test: Accelerated Hashing -- SHA-256 / RIPEMD-160 / Hash160
// ============================================================================
// Validates correctness against known test vectors (NIST, Bitcoin).
// Benchmarks scalar vs SHA-NI performance.
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <chrono>
#include <array>
#include "secp256k1/hash_accel.hpp"
#include "secp256k1/sha256.hpp" // reference
#include "secp256k1/point.hpp"
using namespace secp256k1;
static int g_pass = 0, g_fail = 0;
static void check(bool cond, const char* name) {
if (cond) {
++g_pass;
} else {
++g_fail;
(void)std::printf(" FAIL: %s\n", name);
}
}
[[maybe_unused]] static void print_hex(const std::uint8_t* data, std::size_t len) {
for (std::size_t i = 0; i < len; ++i) {
(void)std::printf("%02x", data[i]);
}
}
// -- Test 1: Feature detection ------------------------------------------------
static void test_feature_detection() {
(void)std::printf("[HashAccel] Feature detection...\n");
auto tier = hash::detect_hash_tier();
(void)std::printf(" Hash tier: %s\n", hash::hash_tier_name(tier));
(void)std::printf(" SHA-NI: %s\n", hash::sha_ni_available() ? "yes" : "no");
(void)std::printf(" AVX2: %s\n", hash::avx2_available() ? "yes" : "no");
(void)std::printf(" AVX-512: %s\n", hash::avx512_available() ? "yes" : "no");
check(true, "feature detection completed");
}
// -- Test 2: SHA-256 known vectors --------------------------------------------
static void test_sha256_vectors() {
(void)std::printf("[HashAccel] SHA-256 known vectors...\n");
// NIST vector: SHA256("abc")
{
auto h = hash::sha256("abc", 3);
std::uint8_t expected[32] = {
0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea,
0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23,
0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c,
0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad
};
check(std::memcmp(h.data(), expected, 32) == 0, "SHA256(\"abc\") NIST vector");
}
// SHA256("")
{
auto h = hash::sha256("", 0);
std::uint8_t expected[32] = {
0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14,
0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24,
0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c,
0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55
};
check(std::memcmp(h.data(), expected, 32) == 0, "SHA256(\"\") empty vector");
}
}
// -- Test 3: sha256_33 vs reference -------------------------------------------
static void test_sha256_33_correctness() {
(void)std::printf("[HashAccel] sha256_33 correctness...\n");
// Generate a compressed pubkey from 1*G
auto gen = fast::Point::generator();
auto compressed = gen.to_compressed();
// Reference: existing SHA256 class
auto ref = SHA256::hash(compressed.data(), 33);
// Accelerated
std::uint8_t accel[32];
hash::sha256_33(compressed.data(), accel);
check(std::memcmp(ref.data(), accel, 32) == 0, "sha256_33(G_compressed) matches reference");
// Scalar implementation
std::uint8_t scalar_out[32];
hash::scalar::sha256_33(compressed.data(), scalar_out);
check(std::memcmp(ref.data(), scalar_out, 32) == 0, "scalar::sha256_33 matches reference");
// Test with multiple points
auto pt = gen;
for (int i = 0; i < 100; ++i) {
auto comp = pt.to_compressed();
auto r = SHA256::hash(comp.data(), 33);
hash::sha256_33(comp.data(), accel);
char label[80];
(void)std::snprintf(label, sizeof(label), "sha256_33(%dG) correct", i + 1);
check(std::memcmp(r.data(), accel, 32) == 0, label);
pt.next_inplace();
}
}
// -- Test 4: sha256_32 correctness --------------------------------------------
static void test_sha256_32_correctness() {
(void)std::printf("[HashAccel] sha256_32 correctness...\n");
std::uint8_t data[32];
for (int i = 0; i < 32; ++i) data[i] = static_cast<std::uint8_t>(i);
auto ref = SHA256::hash(data, 32);
std::uint8_t accel[32];
hash::sha256_32(data, accel);
check(std::memcmp(ref.data(), accel, 32) == 0, "sha256_32 matches reference");
}
// -- Test 5: RIPEMD-160 known vectors -----------------------------------------
static void test_ripemd160_vectors() {
(void)std::printf("[HashAccel] RIPEMD-160 known vectors...\n");
// RIPEMD-160("abc") = 8eb208f7e05d987a9b044a8e98c6b087f15a0bfc
{
auto h = hash::ripemd160("abc", 3);
std::uint8_t expected[20] = {
0x8e, 0xb2, 0x08, 0xf7, 0xe0, 0x5d, 0x98, 0x7a,
0x9b, 0x04, 0x4a, 0x8e, 0x98, 0xc6, 0xb0, 0x87,
0xf1, 0x5a, 0x0b, 0xfc
};
check(std::memcmp(h.data(), expected, 20) == 0, "RIPEMD160(\"abc\") known vector");
}
// RIPEMD-160("") = 9c1185a5c5e9fc54612808977ee8f548b2258d31
{
auto h = hash::ripemd160("", 0);
std::uint8_t expected[20] = {
0x9c, 0x11, 0x85, 0xa5, 0xc5, 0xe9, 0xfc, 0x54,
0x61, 0x28, 0x08, 0x97, 0x7e, 0xe8, 0xf5, 0x48,
0xb2, 0x25, 0x8d, 0x31
};
check(std::memcmp(h.data(), expected, 20) == 0, "RIPEMD160(\"\") known vector");
}
// RIPEMD-160("message digest") = 5d0689ef49d2fae572b881b123a85ffa21595f36
{
const char* msg = "message digest";
auto h = hash::ripemd160(msg, std::strlen(msg));
std::uint8_t expected[20] = {
0x5d, 0x06, 0x89, 0xef, 0x49, 0xd2, 0xfa, 0xe5,
0x72, 0xb8, 0x81, 0xb1, 0x23, 0xa8, 0x5f, 0xfa,
0x21, 0x59, 0x5f, 0x36
};
check(std::memcmp(h.data(), expected, 20) == 0, "RIPEMD160(\"message digest\") known vector");
}
}
// -- Test 6: ripemd160_32 correctness -----------------------------------------
static void test_ripemd160_32_correctness() {
(void)std::printf("[HashAccel] ripemd160_32 correctness...\n");
// Hash 32 zero bytes via general API
std::uint8_t zeros[32] = {};
auto ref = hash::ripemd160(zeros, 32);
std::uint8_t fast_out[20];
hash::ripemd160_32(zeros, fast_out);
check(std::memcmp(ref.data(), fast_out, 20) == 0, "ripemd160_32(zeros) matches general API");
// Hash some non-zero data
std::uint8_t data[32];
for (int i = 0; i < 32; ++i) data[i] = static_cast<std::uint8_t>(i * 7 + 13);
auto ref2 = hash::ripemd160(data, 32);
hash::ripemd160_32(data, fast_out);
check(std::memcmp(ref2.data(), fast_out, 20) == 0, "ripemd160_32(data) matches general API");
}
// -- Test 7: Hash160 pipeline -------------------------------------------------
static void test_hash160_pipeline() {
(void)std::printf("[HashAccel] Hash160 pipeline correctness...\n");
// Compute Hash160 of 1*G compressed pubkey via two methods:
// Method 1: hash::hash160_33
// Method 2: SHA256 -> RIPEMD160 manually
auto gen = fast::Point::generator();
auto comp = gen.to_compressed();
// Manual: SHA256 then RIPEMD160
auto sha_out = SHA256::hash(comp.data(), 33);
auto manual_rmd = hash::ripemd160(sha_out.data(), 32);
// Accelerated hash160_33
std::uint8_t accel[20];
hash::hash160_33(comp.data(), accel);
check(std::memcmp(manual_rmd.data(), accel, 20) == 0, "hash160_33(G) matches manual SHA+RMD");
// Generic hash160
auto generic = hash::hash160(comp.data(), 33);
check(std::memcmp(generic.data(), accel, 20) == 0, "hash160(G) matches hash160_33(G)");
// Test N points
auto pt = gen;
for (int i = 0; i < 50; ++i) {
auto c = pt.to_compressed();
auto ref_sha = SHA256::hash(c.data(), 33);
auto ref_rmd = hash::ripemd160(ref_sha.data(), 32);
hash::hash160_33(c.data(), accel);
char label[80];
(void)std::snprintf(label, sizeof(label), "hash160_33(%dG) correct", i + 1);
check(std::memcmp(ref_rmd.data(), accel, 20) == 0, label);
pt.next_inplace();
}
}
// -- Test 8: Batch operations -------------------------------------------------
static void test_batch_ops() {
(void)std::printf("[HashAccel] Batch operations...\n");
constexpr std::size_t COUNT = 256;
// Generate pubkeys
std::array<std::uint8_t, COUNT * 33> pubkeys;
auto pt = fast::Point::generator();
for (std::size_t i = 0; i < COUNT; ++i) {
auto comp = pt.to_compressed();
std::memcpy(pubkeys.data() + i * 33, comp.data(), 33);
pt.next_inplace();
}
// Batch SHA256
std::array<std::uint8_t, COUNT * 32> sha_results;
hash::sha256_33_batch(pubkeys.data(), sha_results.data(), COUNT);
// Verify each
for (std::size_t i = 0; i < COUNT; ++i) {
auto ref = SHA256::hash(pubkeys.data() + i * 33, 33);
char label[80];
(void)std::snprintf(label, sizeof(label), "sha256_33_batch[%zu]", i);
check(std::memcmp(ref.data(), sha_results.data() + i * 32, 32) == 0, label);
}
// Batch Hash160
std::array<std::uint8_t, COUNT * 20> h160_results;
hash::hash160_33_batch(pubkeys.data(), h160_results.data(), COUNT);
// Verify each
pt = fast::Point::generator();
for (std::size_t i = 0; i < COUNT; ++i) {
auto comp = pt.to_compressed();
auto ref_sha = SHA256::hash(comp.data(), 33);
auto ref_rmd = hash::ripemd160(ref_sha.data(), 32);
char label[80];
(void)std::snprintf(label, sizeof(label), "hash160_33_batch[%zu]", i);
check(std::memcmp(ref_rmd.data(), h160_results.data() + i * 20, 20) == 0, label);
pt.next_inplace();
}
}
// -- Test 9: SHA-NI vs Scalar cross-check -------------------------------------
static void test_shani_vs_scalar() {
(void)std::printf("[HashAccel] SHA-NI vs Scalar cross-check...\n");
#ifdef SECP256K1_X86_TARGET
if (!hash::sha_ni_available()) {
(void)std::printf(" SHA-NI not available, skipping\n");
check(true, "SHA-NI not available (skip)");
return;
}
auto gen = fast::Point::generator();
auto pt = gen;
for (int i = 0; i < 200; ++i) {
auto comp = pt.to_compressed();
std::uint8_t scalar_out[32], shani_out[32];
hash::scalar::sha256_33(comp.data(), scalar_out);
hash::shani::sha256_33(comp.data(), shani_out);
char label[80];
(void)std::snprintf(label, sizeof(label), "SHA-NI == Scalar for %dG", i + 1);
check(std::memcmp(scalar_out, shani_out, 32) == 0, label);
pt.next_inplace();
}
#else
(void)std::printf(" Not x86, skipping SHA-NI test\n");
check(true, "Not x86 (skip)");
#endif
}
// -- Test 10: Benchmark -------------------------------------------------------
static void test_benchmark() {
(void)std::printf("[HashAccel] Benchmark...\n");
// Prepare a typical compressed pubkey
auto gen = fast::Point::generator();
auto comp = gen.to_compressed();
std::uint8_t out32[32], out20[20];
constexpr int ITERS = 100000;
// Scalar SHA256_33
{
auto t0 = std::chrono::high_resolution_clock::now();
for (int i = 0; i < ITERS; ++i) {
hash::scalar::sha256_33(comp.data(), out32);
}
auto t1 = std::chrono::high_resolution_clock::now();
double const ns = static_cast<double>(std::chrono::duration_cast<std::chrono::nanoseconds>(t1 - t0).count());
(void)std::printf(" Scalar SHA256_33: %.1f ns/call\n", ns / ITERS);
}
// Auto-dispatch SHA256_33
{
auto t0 = std::chrono::high_resolution_clock::now();
for (int i = 0; i < ITERS; ++i) {
hash::sha256_33(comp.data(), out32);
}
auto t1 = std::chrono::high_resolution_clock::now();
double const ns = static_cast<double>(std::chrono::duration_cast<std::chrono::nanoseconds>(t1 - t0).count());
(void)std::printf(" Auto SHA256_33: %.1f ns/call (%s)\n", ns / ITERS, hash::hash_tier_name(hash::detect_hash_tier()));
}
// Scalar RIPEMD160_32
{
auto t0 = std::chrono::high_resolution_clock::now();
for (int i = 0; i < ITERS; ++i) {
hash::scalar::ripemd160_32(out32, out20);
}
auto t1 = std::chrono::high_resolution_clock::now();
double const ns = static_cast<double>(std::chrono::duration_cast<std::chrono::nanoseconds>(t1 - t0).count());
(void)std::printf(" Scalar RIPEMD160_32: %.1f ns/call\n", ns / ITERS);
}
// Hash160_33 (fused SHA256+RIPEMD160)
{
auto t0 = std::chrono::high_resolution_clock::now();
for (int i = 0; i < ITERS; ++i) {
hash::hash160_33(comp.data(), out20);
}
auto t1 = std::chrono::high_resolution_clock::now();
double const ns = static_cast<double>(std::chrono::duration_cast<std::chrono::nanoseconds>(t1 - t0).count());
(void)std::printf(" Auto Hash160_33: %.1f ns/call\n", ns / ITERS);
}
// Old SHA256 class (reference baseline)
{
auto t0 = std::chrono::high_resolution_clock::now();
for (int i = 0; i < ITERS; ++i) {
auto h = SHA256::hash(comp.data(), 33);
(void)h;
}
auto t1 = std::chrono::high_resolution_clock::now();
double const ns = static_cast<double>(std::chrono::duration_cast<std::chrono::nanoseconds>(t1 - t0).count());
(void)std::printf(" Old SHA256::hash: %.1f ns/call (reference)\n", ns / ITERS);
}
// Batch Hash160
{
constexpr std::size_t BATCH = 1024;
std::array<std::uint8_t, BATCH * 33> keys;
std::array<std::uint8_t, BATCH * 20> hashes;
auto pt = gen;
for (std::size_t i = 0; i < BATCH; ++i) {
auto c = pt.to_compressed();
std::memcpy(keys.data() + i * 33, c.data(), 33);
pt.next_inplace();
}
// Warmup
hash::hash160_33_batch(keys.data(), hashes.data(), BATCH);
constexpr int BATCH_ITERS = 1000;
auto t0 = std::chrono::high_resolution_clock::now();
for (int i = 0; i < BATCH_ITERS; ++i) {
hash::hash160_33_batch(keys.data(), hashes.data(), BATCH);
}
auto t1 = std::chrono::high_resolution_clock::now();
double const total_ns = static_cast<double>(std::chrono::duration_cast<std::chrono::nanoseconds>(t1 - t0).count());
double const per_key = total_ns / BATCH_ITERS / BATCH;
(void)std::printf(" Batch Hash160_33 (%zu): %.1f ns/key, %.2f Mkeys/s\n",
BATCH, per_key, 1e9 / per_key / 1e6);
}
check(true, "benchmark complete");
}
// -- Test 11: Double-SHA256 ---------------------------------------------------
static void test_double_sha256() {
(void)std::printf("[HashAccel] Double-SHA256...\n");
std::uint8_t data[] = {0x01, 0x02, 0x03};
auto ref1 = SHA256::hash(data, 3);
auto ref2 = SHA256::hash(ref1.data(), 32);
auto accel = hash::sha256d(data, 3);
check(std::memcmp(ref2.data(), accel.data(), 32) == 0, "sha256d matches SHA256(SHA256(data))");
}
// -- Entry point --------------------------------------------------------------
int test_hash_accel_run() {
(void)std::printf("\n=== Accelerated Hashing Tests ===\n");
test_feature_detection();
test_sha256_vectors();
test_sha256_33_correctness();
test_sha256_32_correctness();
test_ripemd160_vectors();
test_ripemd160_32_correctness();
test_hash160_pipeline();
test_double_sha256();
test_batch_ops();
test_shani_vs_scalar();
test_benchmark();
(void)std::printf("\n Hash accel: %d passed, %d failed\n", g_pass, g_fail);
return g_fail;
}
#ifdef STANDALONE_TEST
int main() {
return test_hash_accel_run();
}
#endif