mirror of
https://codeberg.org/wowario/wownero
synced 2026-10-23 06:38:42 +02:00
810 lines
29 KiB
C++
810 lines
29 KiB
C++
#include <array>
|
|
#include <cerrno>
|
|
#include <chrono>
|
|
#include <cstddef>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <fstream>
|
|
#include <iomanip>
|
|
#include <ios>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
#include <stdexcept>
|
|
#include <thread>
|
|
#include <atomic>
|
|
#include <random>
|
|
#include <mutex>
|
|
|
|
#if defined(_MSC_VER) && !defined(__clang__)
|
|
#include <intrin.h>
|
|
#pragma intrinsic(__popcnt)
|
|
#endif
|
|
|
|
static inline unsigned pc8(unsigned x) {
|
|
#if defined(_MSC_VER) && !defined(__clang__)
|
|
return __popcnt(x);
|
|
#elif defined(__GNUC__) || defined(__clang__)
|
|
return __builtin_popcount(x);
|
|
#else
|
|
// SW fallback
|
|
x = x - ((x >> 1) & 0x55555555u);
|
|
x = (x & 0x33333333u) + ((x >> 2) & 0x33333333u);
|
|
return (((x + (x >> 4)) & 0x0F0F0F0Fu) * 0x01010101u) >> 24;
|
|
#endif
|
|
}
|
|
|
|
#include "misc_log_ex.h"
|
|
#include "warnings.h"
|
|
#include "string_tools.h"
|
|
#include "common/util.h"
|
|
#include "crypto/hash.h"
|
|
#include "crypto/wbag-hash.h"
|
|
#include "cryptonote_basic/wbag_cache.h"
|
|
#include "wbag_test.h"
|
|
|
|
extern "C" {
|
|
void WBAG_set_aes_mode(int mode);
|
|
int WBAG_get_aes_mode(void);
|
|
int WBAG_hw_aes_available(void);
|
|
}
|
|
|
|
using namespace std;
|
|
using namespace crypto;
|
|
|
|
// Hardcoded data
|
|
uint32_t params_signature = 0xb8e4f207u;
|
|
|
|
struct wbag_anchor_sample {
|
|
uint64_t height;
|
|
const char *block_id_hex;
|
|
};
|
|
|
|
static constexpr wbag_anchor_sample k_wbag_anchor_samples[] = {
|
|
{770976, "731cabafc8626ce567be7d2ce161d0fefdaa0e6a4f88275513f199362b8d53fd"},
|
|
{770832, "c8710d1ff2b98134c4777e1bbc8dc90ddb6d55a2be850dc904c20218cdb5a970"},
|
|
{770688, "f54b797ed2c44e2c37a455118df2eef4656d159ac18ed8aa5de1affac2f5c20b"},
|
|
{770544, "5197fb807e62e83a5e50e155ddb9975066aca574e22aa6e2f5ccc0a380f1618a"},
|
|
{770400, "4b2981fd2b65aeeb28181ba37c1e49181db4123060f1fa205124aa78e77e4963"},
|
|
};
|
|
|
|
static const std::unordered_map<uint64_t, std::string> kGoldenCacheSHA256 = {
|
|
{770976, "add71b5d2713a5c03b20b37e070ef2955211de7392537a8ed2250a8dcf0fa49d"},
|
|
{770832, "64f2ebe4fc0ccdb7236eb20887db508f04265da7ead73928d436eb59d92d673d"},
|
|
{770688, "720bab8f69866dc7fc0ea277e76494139c5f70da6d3b54096639b4e1b85cac8b"},
|
|
{770544, "d2001885d690843454f24391ab9f025b1ceaafe3d348c82e26c20d16127d3c43"},
|
|
{770400, "ef75d47acfaa24e810fb87228947fc3e0547938bf824c254d093de64d3dd1590"},
|
|
};
|
|
|
|
// Helpers
|
|
static inline bool is_hex_digit(char c) {
|
|
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
|
|
}
|
|
|
|
static std::string trim(const std::string& s) {
|
|
const auto a = s.find_first_not_of(" \t\r\n");
|
|
if (a == std::string::npos) return {};
|
|
const auto b = s.find_last_not_of(" \t\r\n");
|
|
return s.substr(a, b - a + 1);
|
|
}
|
|
|
|
static std::string lower_hex(const uint8_t* p, size_t n) {
|
|
std::ostringstream o;
|
|
o << std::hex << std::setfill('0');
|
|
for (size_t i = 0; i < n; ++i) o << std::setw(2) << unsigned(p[i]);
|
|
return o.str();
|
|
}
|
|
|
|
static inline int hex_nibble(char c) {
|
|
if (c >= '0' && c <= '9') return c - '0';
|
|
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
|
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
|
return -1;
|
|
}
|
|
|
|
static std::vector<uint8_t> hex_to_bytes(const std::string& hex) {
|
|
if ((hex.size() & 1) != 0) {
|
|
std::cerr << "vectors: odd-length hex input\n"; std::exit(1);
|
|
}
|
|
std::vector<uint8_t> out(hex.size()/2);
|
|
for (size_t i = 0, j = 0; i < hex.size(); i += 2, ++j) {
|
|
const int hi = hex_nibble(hex[i]);
|
|
const int lo = hex_nibble(hex[i+1]);
|
|
if (hi < 0 || lo < 0) { std::cerr << "vectors: non-hex char in input\n"; std::exit(1); }
|
|
out[j] = static_cast<uint8_t>((hi << 4) | lo);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
static void test_wbag_params_signature(uint32_t got, uint32_t expect = params_signature) {
|
|
if (got != expect) {
|
|
std::cerr << "params: signature mismatch\n"
|
|
<< " expected: 0x" << std::hex << expect
|
|
<< "\n got: 0x" << got << std::dec << "\n";
|
|
std::exit(1);
|
|
}
|
|
}
|
|
|
|
static bool ensure_wbag_cache()
|
|
{
|
|
static std::once_flag once;
|
|
static bool ready = false;
|
|
|
|
std::call_once(once, []{
|
|
for (const auto &sample : k_wbag_anchor_samples) {
|
|
crypto::hash anchor{};
|
|
if (!epee::string_tools::hex_to_pod(sample.block_id_hex, anchor)) continue;
|
|
const uint64_t ep = cryptonote::wbag::epoch(sample.height);
|
|
if (!cryptonote::wbag::ensure_cache(ep, anchor)) continue;
|
|
auto view = cryptonote::wbag::cache_view();
|
|
if (view && view.data()) { ready = true; break; }
|
|
}
|
|
});
|
|
|
|
auto view = cryptonote::wbag::cache_view();
|
|
return ready && view && view.data();
|
|
}
|
|
|
|
void wbag_hash(const void *data, size_t length, char *hash)
|
|
{
|
|
if (!ensure_wbag_cache())
|
|
throw std::runtime_error("WBAG cache unavailable");
|
|
|
|
auto cached = cryptonote::wbag::cache_view();
|
|
if (!cached || !cached.data())
|
|
throw std::runtime_error("WBAG cache view invalid");
|
|
|
|
if (WBAG_hash(data, length, cached.data(), reinterpret_cast<uint8_t*>(hash)) != 0)
|
|
throw std::runtime_error("WBAG_hash failed");
|
|
}
|
|
|
|
static bool write_cache_and_sha256(const cryptonote::wbag::CacheView& view, std::string& sha_hex, std::string& note) {
|
|
static constexpr const char *k_cache_file = "wbag_cache.dat";
|
|
{
|
|
std::ofstream out(k_cache_file, std::ios::binary | std::ios::trunc);
|
|
if (!out) { note = "write cache file failed"; return false; }
|
|
out.write(reinterpret_cast<const char*>(view.data()), static_cast<std::streamsize>(view.size()));
|
|
if (!out.good()) { note = "write cache bytes failed"; return false; }
|
|
}
|
|
|
|
crypto::hash file_hash{};
|
|
if (!tools::sha256sum(k_cache_file, file_hash)) {
|
|
note = "sha256sum failed";
|
|
(void)std::remove(k_cache_file);
|
|
return false;
|
|
}
|
|
sha_hex = epee::string_tools::pod_to_hex(file_hash);
|
|
(void)std::remove(k_cache_file);
|
|
return true;
|
|
}
|
|
|
|
static bool sha256_file(const char* path, std::string& sha_hex, std::string& note) {
|
|
crypto::hash h{};
|
|
if (!tools::sha256sum(path, h)) { note = "sha256sum(file) failed"; return false; }
|
|
sha_hex = epee::string_tools::pod_to_hex(h);
|
|
return true;
|
|
}
|
|
|
|
static bool sha256_bytes_tmp(const uint8_t* data, size_t len, std::string& sha_hex, std::string& note) {
|
|
static constexpr const char *k_tmp = "wbag_cache.tmp";
|
|
{
|
|
std::ofstream out(k_tmp, std::ios::binary | std::ios::trunc);
|
|
if (!out) { note = "tmp file open failed"; return false; }
|
|
out.write(reinterpret_cast<const char*>(data), static_cast<std::streamsize>(len));
|
|
if (!out.good()) { note = "tmp file write failed"; (void)std::remove(k_tmp); return false; }
|
|
}
|
|
const bool ok = sha256_file(k_tmp, sha_hex, note);
|
|
(void)std::remove(k_tmp);
|
|
return ok;
|
|
}
|
|
|
|
static bool build_cache_with_threads(uint64_t height, const crypto::hash& anchor, unsigned T, std::vector<uint8_t>& buf, std::string& note, double* build_sec /*=nullptr*/)
|
|
{
|
|
note.clear();
|
|
|
|
uint8_t seed[32];
|
|
WBAG_epoch_seed(cryptonote::wbag::epoch(height), reinterpret_cast<const uint8_t*>(&anchor), seed);
|
|
|
|
const uint64_t bytes64 = WBAG_cache_bytes();
|
|
const uint32_t entry_b = WBAG_entry_bytes();
|
|
if (entry_b == 0 || bytes64 == 0 || (bytes64 % entry_b) != 0) { note = "geometry invalid"; return false; }
|
|
const uint64_t n_entries = bytes64 / entry_b;
|
|
|
|
if (T < 1) T = 1;
|
|
if (T > n_entries) T = static_cast<unsigned>(n_entries);
|
|
|
|
try { buf.resize(static_cast<size_t>(bytes64)); }
|
|
catch (...) { note = "alloc failed"; return false; }
|
|
|
|
const auto start_build = std::chrono::high_resolution_clock::now();
|
|
if (T == 1) { WBAG_build_cache(seed, buf.data(), 0, n_entries); }
|
|
else {
|
|
std::atomic<bool> failed{false};
|
|
std::vector<std::thread> workers;
|
|
workers.reserve(T);
|
|
|
|
for (unsigned i = 0; i < T; ++i) {
|
|
const uint64_t start = (static_cast<uint64_t>(i) * n_entries) / T;
|
|
const uint64_t end = (static_cast<uint64_t>(i + 1) * n_entries) / T;
|
|
workers.emplace_back([&, start, end]() noexcept {
|
|
try { WBAG_build_cache(seed, buf.data(), start, end); }
|
|
catch (...) { failed.store(true, std::memory_order_relaxed); }
|
|
});
|
|
}
|
|
|
|
for (auto &th : workers) {
|
|
try { th.join(); } catch (...) { failed.store(true, std::memory_order_relaxed); }
|
|
}
|
|
|
|
if (failed.load(std::memory_order_relaxed)) { note = "worker failure"; return false; }
|
|
}
|
|
if (build_sec) *build_sec = std::chrono::duration<double>(std::chrono::high_resolution_clock::now() - start_build).count();
|
|
return true;
|
|
}
|
|
|
|
static std::vector<unsigned> choose_thread_counts() {
|
|
unsigned L = std::thread::hardware_concurrency(); if (!L) L = 1;
|
|
unsigned P = (L >= 2) ? (L / 2) : 1;
|
|
|
|
const uint32_t entry_b = WBAG_entry_bytes();
|
|
const uint64_t cache_b = WBAG_cache_bytes();
|
|
if (entry_b == 0 || cache_b == 0) return {1u};
|
|
|
|
const uint64_t n_entries = cache_b / entry_b;
|
|
if (n_entries == 0) return {1u};
|
|
|
|
std::vector<unsigned> ts = {1u, P, std::min<unsigned>(P + 1, L), L};
|
|
std::sort(ts.begin(), ts.end());
|
|
ts.erase(std::unique(ts.begin(), ts.end()), ts.end());
|
|
ts.erase(std::remove_if(ts.begin(), ts.end(), [&](unsigned t){ return t == 0 || t > n_entries; }), ts.end());
|
|
if (ts.empty()) ts.push_back(1u);
|
|
return ts;
|
|
}
|
|
|
|
static void benchmark_hash(const cryptonote::wbag::CacheView& view, int trials, const std::vector<uint8_t>& block, double& perf_khs_out, std::string& last_digest_hex_out) {
|
|
std::vector<uint8_t> out(crypto::HASH_SIZE);
|
|
|
|
// Warm-up
|
|
for (int i = 0; i < 64; ++i)
|
|
WBAG_hash(block.data(), block.size(), view.data(), out.data());
|
|
|
|
// Timed runs: median of 3
|
|
double times[3];
|
|
for (int r = 0; r < 3; ++r) {
|
|
const auto t0 = std::chrono::high_resolution_clock::now();
|
|
if (trials <= 512) {
|
|
for (int i = 0; i < trials; ++i) {
|
|
WBAG_hash(block.data(), block.size(), view.data(), out.data());
|
|
if (((i + 1) % 8) == 0) std::cerr << "." << std::flush;
|
|
}
|
|
} else {
|
|
for (int i = 0; i < trials; ++i)
|
|
WBAG_hash(block.data(), block.size(), view.data(), out.data());
|
|
}
|
|
times[r] = std::chrono::duration<double>(std::chrono::high_resolution_clock::now() - t0).count();
|
|
}
|
|
std::sort(std::begin(times), std::end(times));
|
|
const double elapsed = times[1];
|
|
perf_khs_out = (trials / elapsed) / 1000.0;
|
|
|
|
crypto::hash h{};
|
|
static_assert(sizeof(h) == crypto::HASH_SIZE, "hash size mismatch");
|
|
std::memcpy(&h, out.data(), crypto::HASH_SIZE);
|
|
last_digest_hex_out = epee::string_tools::pod_to_hex(h);
|
|
|
|
std::cerr << " - Hash rate: " << std::fixed << std::setprecision(2) << perf_khs_out << " kH/s\n";
|
|
}
|
|
|
|
// Test units
|
|
static void test_wbag_check_vectors(const std::string& path = "tests/hash/tests-wbag.txt") {
|
|
std::ifstream in(path);
|
|
if (!in) {
|
|
std::cerr << "vectors: cannot open " << path << "\n"; std::exit(1);
|
|
}
|
|
|
|
size_t cases = 0;
|
|
std::string line, pending_hash;
|
|
|
|
auto process_pair = [&](const std::string& expect_hex, const std::string& input_hex) {
|
|
const std::string expect = trim(expect_hex);
|
|
const std::string input = trim(input_hex);
|
|
|
|
if (expect.size() != 64) {
|
|
std::cerr << "vectors: expected-hash len != 64\n"; std::exit(1);
|
|
}
|
|
for (char c : expect) if (!is_hex_digit(c)) {
|
|
std::cerr << "vectors: expected-hash not hex\n"; std::exit(1);
|
|
}
|
|
|
|
const auto msg = input.empty() ? std::vector<uint8_t>{} : hex_to_bytes(input);
|
|
|
|
crypto::hash out{};
|
|
wbag_hash(msg.empty() ? nullptr : msg.data(), msg.size(),
|
|
reinterpret_cast<char*>(&out));
|
|
|
|
const std::string got = lower_hex(reinterpret_cast<const uint8_t*>(&out), sizeof(out));
|
|
if (!std::equal(expect.begin(), expect.end(), got.begin(), got.end(),
|
|
[](char a, char b){
|
|
return std::tolower(static_cast<unsigned char>(a)) == std::tolower(static_cast<unsigned char>(b));
|
|
})) {
|
|
std::cerr << "vectors: mismatch at case " << (cases+1) << "\n" << " expected: " << expect << "\n" << " got: " << got << "\n";
|
|
std::exit(1);
|
|
}
|
|
++cases;
|
|
};
|
|
|
|
while (std::getline(in, line)) {
|
|
std::string s = trim(line);
|
|
if (s.empty() || s[0] == '#') continue;
|
|
|
|
const auto sp = s.find(' ');
|
|
if (sp != std::string::npos) {
|
|
const std::string left = s.substr(0, sp);
|
|
const std::string right = s.substr(sp + 1);
|
|
process_pair(left, right);
|
|
continue;
|
|
}
|
|
|
|
if (pending_hash.empty()) {
|
|
pending_hash = s;
|
|
continue;
|
|
} else {
|
|
process_pair(pending_hash, s);
|
|
pending_hash.clear();
|
|
}
|
|
}
|
|
|
|
if (!pending_hash.empty()) {
|
|
process_pair(pending_hash, "");
|
|
pending_hash.clear();
|
|
}
|
|
}
|
|
|
|
static void test_wbag_determinism(const cryptonote::wbag::CacheView& view) {
|
|
std::array<uint8_t, crypto::HASH_SIZE> out0{}, out1{};
|
|
const std::vector<uint8_t> msg(80, 0x42);
|
|
if (WBAG_hash(msg.data(), msg.size(), view.data(), out0.data()) != 0) {
|
|
std::cerr << "determinism: first hash failed\n"; std::exit(1);
|
|
}
|
|
for (int i = 0; i < 31; ++i) {
|
|
if (WBAG_hash(msg.data(), msg.size(), view.data(), out1.data()) != 0) {
|
|
std::cerr << "determinism: repeat hash failed\n"; std::exit(1);
|
|
}
|
|
if (memcmp(out0.data(), out1.data(), crypto::HASH_SIZE) != 0) {
|
|
std::cerr << "determinism: mismatch on repeat " << i << "\n"; std::exit(1);
|
|
}
|
|
}
|
|
std::cerr << " - Determinism: OK\n";
|
|
}
|
|
|
|
static void test_wbag_edge_inputs(const cryptonote::wbag::CacheView& view) {
|
|
std::array<uint8_t, crypto::HASH_SIZE> out{};
|
|
if (WBAG_hash(nullptr, 0, view.data(), out.data()) != 0) {
|
|
std::cerr << "edge: len=0 failed\n"; std::exit(1);
|
|
}
|
|
uint8_t buf[66]; for (int i = 0; i < 66; ++i) buf[i] = static_cast<uint8_t>(i);
|
|
const size_t lens[] = {1, 63, 64, 65};
|
|
for (size_t L : lens) {
|
|
if (WBAG_hash(buf + 1, L, view.data(), out.data()) != 0) {
|
|
std::cerr << "edge: len=" << L << " failed\n"; std::exit(1);
|
|
}
|
|
}
|
|
std::cerr << " - Edge inputs: OK\n";
|
|
}
|
|
|
|
static void test_wbag_api_errors(const cryptonote::wbag::CacheView& view) {
|
|
std::array<uint8_t, crypto::HASH_SIZE> out{};
|
|
errno = 0;
|
|
if (WBAG_hash(nullptr, 4, view.data(), out.data()) != -1 || errno != EINVAL) {
|
|
std::cerr << "api: expected EINVAL for bd=null,len>0\n"; std::exit(1);
|
|
}
|
|
errno = 0;
|
|
if (WBAG_hash(nullptr, 0, nullptr, out.data()) != -1 || errno != EINVAL) {
|
|
std::cerr << "api: expected EINVAL for cache=null\n"; std::exit(1);
|
|
}
|
|
errno = 0;
|
|
if (WBAG_hash(nullptr, 0, view.data(), nullptr) != -1 || errno != EINVAL) {
|
|
std::cerr << "api: expected EINVAL for out32=null\n"; std::exit(1);
|
|
}
|
|
std::cerr << " - API errors check: OK\n";
|
|
}
|
|
|
|
static void test_wbag_cache_sha_stability(uint64_t height,
|
|
const std::string& sha_hex_again) {
|
|
static std::unordered_map<uint64_t, std::string> seen;
|
|
auto it = seen.find(height);
|
|
if (it == seen.end()) {
|
|
seen.emplace(height, sha_hex_again);
|
|
} else if (it->second != sha_hex_again) {
|
|
std::cerr << "cache-sha: instability for height " << height << "\n"; std::exit(1);
|
|
}
|
|
for (const auto& kv : seen) {
|
|
if (kv.first != height && kv.second == sha_hex_again) {
|
|
std::cerr << "cache-sha: SHA collision across anchors " << kv.first << " and " << height << "\n"; std::exit(1);
|
|
}
|
|
}
|
|
std::cerr << " - Cache stability/distinctness: OK\n";
|
|
}
|
|
|
|
static void test_wbag_cache_hash_check(uint64_t height, const std::string& sha256_hex) {
|
|
auto it = kGoldenCacheSHA256.find(height);
|
|
if (it == kGoldenCacheSHA256.end()) {
|
|
std::cerr << " - Cache SHA-256 hash match: skipped (no golden)\n";
|
|
return;
|
|
}
|
|
if (sha256_hex != it->second) {
|
|
std::cerr << " - Cache SHA-256 hash match: MISMATCH\n"
|
|
<< " expected: " << it->second << "\n"
|
|
<< " got: " << sha256_hex << "\n";
|
|
std::exit(1);
|
|
}
|
|
std::cerr << " - Cache SHA-256 hash match: OK\n";
|
|
}
|
|
|
|
static void test_wbag_aes_parity_check(const cryptonote::wbag::CacheView& view, const std::vector<uint8_t>& block) {
|
|
if (!view || !view.data() || view.size() == 0) {
|
|
std::cerr << " - AES HW vs SW parity: invalid cache view\n";
|
|
std::exit(1);
|
|
}
|
|
if (!WBAG_hw_aes_available()) {
|
|
std::cerr << " - AES HW vs SW parity: skipped (no AES present)\n";
|
|
return;
|
|
}
|
|
|
|
std::array<uint8_t, crypto::HASH_SIZE> out_hw{}, out_sw{};
|
|
const int prev_mode = WBAG_get_aes_mode();
|
|
|
|
WBAG_set_aes_mode(1);
|
|
if (WBAG_hash(block.data(), block.size(), view.data(), out_hw.data()) != 0) {
|
|
std::cerr << " - AES HW vs SW parity: hash(hardware) failed\n";
|
|
WBAG_set_aes_mode(prev_mode);
|
|
std::exit(1);
|
|
}
|
|
|
|
WBAG_set_aes_mode(0);
|
|
if (WBAG_hash(block.data(), block.size(), view.data(), out_sw.data()) != 0) {
|
|
std::cerr << " - AES HW vs SW parity: hash(software) failed\n";
|
|
WBAG_set_aes_mode(prev_mode);
|
|
std::exit(1);
|
|
}
|
|
|
|
WBAG_set_aes_mode(prev_mode);
|
|
|
|
if (std::memcmp(out_hw.data(), out_sw.data(), crypto::HASH_SIZE) != 0) {
|
|
std::cerr << " - AES HW vs SW parity: MISMATCH\n";
|
|
std::exit(1);
|
|
}
|
|
std::cerr << " - AES HW vs SW parity: OK\n";
|
|
}
|
|
|
|
static void test_wbag_fuzz(const cryptonote::wbag::CacheView& view, uint32_t iters=2000) {
|
|
std::vector<uint8_t> buf(1u<<20);
|
|
std::mt19937_64 rng(0xC0FFEEULL);
|
|
std::uniform_int_distribution<size_t> len_dist(0, buf.size());
|
|
std::uniform_int_distribution<size_t> off_dist(0, 63);
|
|
|
|
for (uint32_t i=0; i<iters; ++i) {
|
|
for (auto& b : buf) b = static_cast<uint8_t>(rng());
|
|
size_t L = len_dist(rng);
|
|
size_t off = std::min(off_dist(rng), L);
|
|
std::array<uint8_t, crypto::HASH_SIZE> out{};
|
|
if (WBAG_hash(buf.data()+off, L-off, view.data(), out.data()) != 0) {
|
|
std::cerr << "fuzz: hash failed at iter " << i << "\n"; std::exit(1);
|
|
}
|
|
}
|
|
std::cerr << " - Fuzz: OK\n";
|
|
}
|
|
|
|
static void test_wbag_avalanche(const cryptonote::wbag::CacheView& view) {
|
|
std::array<uint8_t,64> msg{};
|
|
std::array<uint8_t, crypto::HASH_SIZE> base{}, alt{};
|
|
if (WBAG_hash(msg.data(), msg.size(), view.data(), base.data()) != 0) {
|
|
std::cerr << "avalanche: base hash failed\n"; std::exit(1);
|
|
}
|
|
auto hdist = [](const uint8_t* a, const uint8_t* b){
|
|
unsigned c=0;
|
|
for (size_t i=0;i<crypto::HASH_SIZE;i++) c += pc8(unsigned(a[i]^b[i]));
|
|
return c;
|
|
};
|
|
double sum=0;
|
|
for (int bit=0; bit<64*8; ++bit) {
|
|
msg[bit>>3] ^= (1u<<(bit&7));
|
|
if (WBAG_hash(msg.data(), msg.size(), view.data(), alt.data()) != 0) {
|
|
std::cerr << "avalanche: alt hash failed at bit " << bit << "\n"; std::exit(1);
|
|
}
|
|
sum += hdist(base.data(), alt.data());
|
|
msg[bit>>3] ^= (1u<<(bit&7));
|
|
}
|
|
double avg = sum / (64*8);
|
|
if (avg < 112 || avg > 144) {
|
|
std::cerr << "avalanche: weak diffusion, avg bit flip=" << avg << "\n"; std::exit(1);
|
|
}
|
|
std::cerr << " - Avalanche: OK (avg " << std::fixed << std::setprecision(1) << avg << " bits)\n";
|
|
}
|
|
|
|
static void test_wbag_unaligned_inputs(const cryptonote::wbag::CacheView& view) {
|
|
std::vector<uint8_t> buf(4096);
|
|
for (size_t i = 0; i < buf.size(); ++i) buf[i] = uint8_t(i);
|
|
const size_t L = 1024, off = 37; // misaligned
|
|
std::array<uint8_t, crypto::HASH_SIZE> a{}, b{};
|
|
|
|
if (WBAG_hash(buf.data() + off, L, view.data(), a.data()) != 0) {
|
|
std::cerr << "unaligned: hash(slice) failed\n"; std::exit(1);
|
|
}
|
|
std::vector<uint8_t> tmp(buf.begin() + off, buf.begin() + off + L);
|
|
if (WBAG_hash(tmp.data(), tmp.size(), view.data(), b.data()) != 0) {
|
|
std::cerr << "unaligned: hash(copy) failed\n"; std::exit(1);
|
|
}
|
|
if (std::memcmp(a.data(), b.data(), crypto::HASH_SIZE) != 0) {
|
|
std::cerr << "unaligned: mismatch\n"; std::exit(1);
|
|
}
|
|
std::cerr << " - Unaligned inputs: OK\n";
|
|
}
|
|
|
|
static void test_wbag_large_sizes_smoke(const cryptonote::wbag::CacheView& view) {
|
|
const size_t sizes[] = {0,1,63,64,65,1024, 65536, 1048576, 1048583};
|
|
std::vector<uint8_t> buf(1048583);
|
|
for (size_t i = 0; i < buf.size(); ++i) buf[i] = uint8_t((i * 1315423911u) >> 24);
|
|
|
|
std::array<uint8_t, crypto::HASH_SIZE> out{}, out2{};
|
|
for (size_t s : sizes) {
|
|
if (WBAG_hash(buf.data(), s, view.data(), out.data()) != 0) {
|
|
std::cerr << "large: hash failed at size " << s << "\n"; std::exit(1);
|
|
}
|
|
}
|
|
// Diffusion smoke: flip byte and expect output change
|
|
const size_t pos = 1048576 - 1;
|
|
if (WBAG_hash(buf.data(), 1048576, view.data(), out.data()) != 0) {
|
|
std::cerr << "large: baseline hash failed\n"; std::exit(1);
|
|
}
|
|
buf[pos] ^= 0x80;
|
|
if (WBAG_hash(buf.data(), 1048576, view.data(), out2.data()) != 0) {
|
|
std::cerr << "large: alt hash failed\n"; std::exit(1);
|
|
}
|
|
if (std::memcmp(out.data(), out2.data(), crypto::HASH_SIZE) == 0) {
|
|
std::cerr << "large: no change after byte flip\n"; std::exit(1);
|
|
}
|
|
std::cerr << " - Large-size smoke: OK\n";
|
|
}
|
|
|
|
static void test_wbag_mode_toggle_stability(const cryptonote::wbag::CacheView& view, const std::vector<uint8_t>& block) {
|
|
std::array<uint8_t, crypto::HASH_SIZE> base{}, cur{};
|
|
const int prev = WBAG_get_aes_mode();
|
|
|
|
WBAG_set_aes_mode(-1);
|
|
if (WBAG_hash(block.data(), block.size(), view.data(), base.data()) != 0) {
|
|
std::cerr << "mode-toggle: base hash failed\n"; std::exit(1);
|
|
}
|
|
for (int i = 0; i < 64; ++i) {
|
|
WBAG_set_aes_mode((i & 1) ? 0 : 1);
|
|
if (WBAG_hash(block.data(), block.size(), view.data(), cur.data()) != 0) {
|
|
std::cerr << "mode-toggle: hash failed at iter " << i << "\n"; std::exit(1);
|
|
}
|
|
if (std::memcmp(base.data(), cur.data(), crypto::HASH_SIZE) != 0) {
|
|
std::cerr << "mode-toggle: mismatch at iter " << i << "\n"; std::exit(1);
|
|
}
|
|
}
|
|
WBAG_set_aes_mode(prev);
|
|
std::cerr << " - AES mode toggle stability: OK\n";
|
|
}
|
|
|
|
static void test_wbag_multithread_smoke(const cryptonote::wbag::CacheView& view) {
|
|
const int T = 8, I = 256;
|
|
std::vector<uint8_t> common(80, 0x42);
|
|
std::array<uint8_t, crypto::HASH_SIZE> ref{};
|
|
if (WBAG_hash(common.data(), common.size(), view.data(), ref.data()) != 0) {
|
|
std::cerr << "threaded: ref hash failed\n"; std::exit(1);
|
|
}
|
|
|
|
std::atomic<int> err{0};
|
|
std::vector<std::thread> th;
|
|
th.reserve(T);
|
|
for (int t = 0; t < T; ++t) {
|
|
th.emplace_back([&, t]{
|
|
std::vector<uint8_t> msg(1000 + t);
|
|
for (size_t i = 0; i < msg.size(); ++i) msg[i] = uint8_t((i + t) & 0xFF);
|
|
std::array<uint8_t, crypto::HASH_SIZE> a{}, b{}, c{};
|
|
for (int i = 0; i < I; ++i) {
|
|
if (WBAG_hash(msg.data(), msg.size(), view.data(), a.data()) != 0) { ++err; return; }
|
|
if (WBAG_hash(msg.data(), msg.size(), view.data(), b.data()) != 0) { ++err; return; }
|
|
if (std::memcmp(a.data(), b.data(), crypto::HASH_SIZE) != 0) { ++err; return; }
|
|
if (WBAG_hash(common.data(), common.size(), view.data(), c.data()) != 0) { ++err; return; }
|
|
if (std::memcmp(ref.data(), c.data(), crypto::HASH_SIZE) != 0) { ++err; return; }
|
|
}
|
|
});
|
|
}
|
|
for (auto& x : th) x.join();
|
|
if (err.load() != 0) { std::cerr << "threaded: mismatch\n"; std::exit(1); }
|
|
std::cerr << " - Multithread smoke: OK\n";
|
|
}
|
|
|
|
static void test_wbag_multithread_cache_test() {
|
|
|
|
const wbag_anchor_sample* sample = nullptr;
|
|
for (const auto& s : k_wbag_anchor_samples) { if (s.height == 770976) { sample = &s; break; } }
|
|
if (!sample) { std::cerr << " - Multithread cache test: missing sample 770976\n"; std::exit(1); }
|
|
|
|
crypto::hash anchor{};
|
|
if (!epee::string_tools::hex_to_pod(sample->block_id_hex, anchor)) {
|
|
std::cerr << " - Multithread cache test: bad anchor hex\n"; std::exit(1);
|
|
}
|
|
|
|
const auto Ts = choose_thread_counts();
|
|
std::vector<std::pair<unsigned,double>> times;
|
|
std::vector<std::string> shas;
|
|
|
|
std::string note;
|
|
for (unsigned T : Ts) {
|
|
std::vector<uint8_t> buf;
|
|
double build_sec = 0.0;
|
|
if (!build_cache_with_threads(sample->height, anchor, T, buf, note, &build_sec)) {
|
|
std::cerr << " - Multithread cache test: build(" << T << ") failed: " << note << "\n"; std::exit(1);
|
|
}
|
|
std::string sha;
|
|
if (!sha256_bytes_tmp(buf.data(), buf.size(), sha, note)) {
|
|
std::cerr << " - Multithread cache test: sha(" << T << ") failed: " << note << "\n"; std::exit(1);
|
|
}
|
|
times.emplace_back(T, build_sec);
|
|
shas.emplace_back(std::move(sha));
|
|
}
|
|
|
|
for (size_t i = 1; i < shas.size(); ++i) {
|
|
if (shas[i] != shas[0]) {
|
|
std::cerr << " - Multithread cache test: MISMATCH between T=" << Ts[0] << " and T=" << Ts[i] << "\n"
|
|
<< " sha(" << Ts[0] << "): " << shas[0] << "\n"
|
|
<< " sha(" << Ts[i] << "): " << shas[i] << "\n";
|
|
std::exit(1);
|
|
}
|
|
}
|
|
|
|
std::ostringstream line;
|
|
line << "Multithread cache test: ";
|
|
for (size_t i = 0; i < times.size(); ++i) {
|
|
line << times[i].first << " (" << std::fixed << std::setprecision(3) << times[i].second << " s)";
|
|
if (i + 1 < times.size()) line << ", ";
|
|
}
|
|
std::cerr << line.str() << "\n\n";
|
|
}
|
|
|
|
static void test_wbag_first_use_race() {
|
|
constexpr int T = 16;
|
|
std::atomic<bool> go{false};
|
|
std::atomic<int> err{0};
|
|
std::vector<std::thread> th; th.reserve(T);
|
|
for (int i=0;i<T;++i) {
|
|
th.emplace_back([&]{
|
|
while (!go.load(std::memory_order_acquire)) {}
|
|
std::array<uint8_t, crypto::HASH_SIZE> out{};
|
|
uint8_t b = 0x42;
|
|
try { wbag_hash(&b, 1, reinterpret_cast<char*>(out.data())); }
|
|
catch (...) { ++err; }
|
|
});
|
|
}
|
|
go.store(true, std::memory_order_release);
|
|
for (auto& x: th) x.join();
|
|
if (err.load() != 0) { std::cerr << "first-use race: fail\n"; std::exit(1); }
|
|
}
|
|
|
|
static void test_wbag_geometry() {
|
|
const uint64_t cb = WBAG_cache_bytes();
|
|
const uint32_t eb = WBAG_entry_bytes();
|
|
if (cb == 0 || eb == 0 || (cb % eb) != 0) {
|
|
std::cerr << "geometry: invalid\n"; std::exit(1);
|
|
}
|
|
}
|
|
|
|
|
|
static void run_wbag_tests(const cryptonote::wbag::CacheView& view, uint64_t height, const std::string& sha256_hex, const std::vector<uint8_t>& block) {
|
|
if (!sha256_hex.empty()) {
|
|
test_wbag_cache_sha_stability(height, sha256_hex);
|
|
test_wbag_cache_hash_check(height, sha256_hex);
|
|
}
|
|
test_wbag_determinism(view);
|
|
test_wbag_edge_inputs(view);
|
|
test_wbag_api_errors(view);
|
|
test_wbag_aes_parity_check(view, block);
|
|
test_wbag_fuzz(view);
|
|
test_wbag_avalanche(view);
|
|
test_wbag_unaligned_inputs(view);
|
|
test_wbag_large_sizes_smoke(view);
|
|
test_wbag_mode_toggle_stability(view, block);
|
|
test_wbag_multithread_smoke(view);
|
|
}
|
|
|
|
// Start WBG tests
|
|
int wbag_test() {
|
|
struct Row {
|
|
uint64_t height = 0;
|
|
uint64_t epoch = 0;
|
|
size_t cache_size = 0;
|
|
double ensure_sec = 0.0;
|
|
double perf_khs = 0.0;
|
|
std::string sha256_hex;
|
|
std::string last_digest_hex;
|
|
std::string note;
|
|
};
|
|
|
|
// Params
|
|
const int trials = 2048;
|
|
const std::vector<uint8_t> block(80, 0x42);
|
|
const size_t cache_bytes = WBAG_cache_bytes();
|
|
const double cache_mb = static_cast<double>(cache_bytes) / (1024.0 * 1024.0);
|
|
const uint32_t params_sig = WBAG_params_signature();
|
|
const int total = static_cast<int>(sizeof(k_wbag_anchor_samples) / sizeof(k_wbag_anchor_samples[0]));
|
|
|
|
test_wbag_params_signature(params_sig);
|
|
test_wbag_first_use_race();
|
|
test_wbag_geometry();
|
|
test_wbag_check_vectors();
|
|
std::cerr << "Params: n_anchors=" << total
|
|
<< ", cache_size=" << std::fixed << std::setprecision(2) << cache_mb
|
|
<< " MB, sig=0x" << std::hex << params_sig << std::dec
|
|
<< ", sig=OK, vectors=OK, first_use=OK, geometry=OK\n\n";
|
|
|
|
test_wbag_multithread_cache_test();
|
|
|
|
// Report
|
|
std::ostringstream report;
|
|
report << "\n\n";
|
|
report << std::left << std::setw(12) << "Height" << std::setw(66) << "SHA256 (cache)" << "Digest\n";
|
|
report << std::string(12+66+64, '-') << "\n";
|
|
|
|
for (int idx = 0; idx < total; ++idx) {
|
|
const auto &sample = k_wbag_anchor_samples[idx];
|
|
Row r;
|
|
r.height = sample.height;
|
|
r.epoch = cryptonote::wbag::epoch(sample.height);
|
|
crypto::hash anchor{};
|
|
if (!epee::string_tools::hex_to_pod(sample.block_id_hex, anchor)) {
|
|
r.note = "bad anchor hex";
|
|
} else {
|
|
std::cerr << "[" << (idx+1) << "/" << total << "] height " << r.height << " epoch " << r.epoch << ": generating cache..." << std::flush;
|
|
|
|
if (idx == 0 && total > 1) {
|
|
crypto::hash other{};
|
|
if (epee::string_tools::hex_to_pod(k_wbag_anchor_samples[1].block_id_hex, other)) {
|
|
const uint64_t other_ep = cryptonote::wbag::epoch(k_wbag_anchor_samples[1].height);
|
|
(void)cryptonote::wbag::ensure_cache(other_ep, other);
|
|
}
|
|
}
|
|
|
|
const auto t0 = std::chrono::high_resolution_clock::now();
|
|
if (!cryptonote::wbag::ensure_cache(r.epoch, anchor)) {
|
|
r.note = "ensure_cache failed";
|
|
std::cerr << " FAILED\n";
|
|
} else {
|
|
r.ensure_sec = std::chrono::duration<double>(std::chrono::high_resolution_clock::now() - t0).count();
|
|
std::cerr << " " << std::fixed << std::setprecision(3) << r.ensure_sec << " s\n";
|
|
auto view = cryptonote::wbag::cache_view();
|
|
if (!view || !view.data() || view.size() == 0) {
|
|
r.note = "cache_view invalid";
|
|
} else {
|
|
r.cache_size = view.size();
|
|
|
|
if (!write_cache_and_sha256(view, r.sha256_hex, r.note)) {
|
|
}
|
|
|
|
// Run tests
|
|
run_wbag_tests(view, r.height, r.sha256_hex, block);
|
|
|
|
// Benchmark
|
|
if (r.note.empty()) {
|
|
benchmark_hash(view, trials, block, r.perf_khs, r.last_digest_hex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
report << std::left
|
|
<< std::setw(12) << (r.height ? std::to_string(r.height) : std::string("-"))
|
|
<< std::setw(66) << (r.sha256_hex.empty() ? std::string("-") : r.sha256_hex)
|
|
<< (r.last_digest_hex.empty() ? std::string("-") : r.last_digest_hex)
|
|
<< (r.note.empty() ? "" : (" [" + r.note + "]"))
|
|
<< "\n";
|
|
}
|
|
std::cout << report.str();
|
|
return 0;
|
|
}
|