Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Verify against difficulty #195

Merged
merged 1 commit into from
Oct 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions include/ethash/ethash.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,27 @@ ethash_errc ethash_verify_against_boundary(const struct ethash_epoch_context* co
const union ethash_hash256* header_hash, const union ethash_hash256* mix_hash, uint64_t nonce,
const union ethash_hash256* boundary) NOEXCEPT;

/**
* Verify Ethash validity of a header hash against given difficulty.
*
* This checks if final hash header_hash satisfies difficulty requirement
* header_hash <= (2^256 / difficulty). The checks is implemented by multiplication
* header_hash * difficulty <= 2^256 therefore big-number division is not needed.
* It also checks if the Ethash result produced out of (header_hash, nonce) matches the provided
* mix_hash.
*
* @param difficulty The difficulty number as big-endian 256-bit value. The encoding matches the
* way the difficulty is stored in block headers.
*
* @return Error code: ::ETHASH_SUCCESS if valid, ::ETHASH_INVALID_FINAL_HASH if the final hash
* does not satisfies difficulty, ::ETHASH_INVALID_MIX_HASH if the provided mix hash
* mismatches the computed one.
*/
ethash_errc ethash_verify_against_difficulty(const struct ethash_epoch_context* context,
const union ethash_hash256* header_hash, const union ethash_hash256* mix_hash, uint64_t nonce,
const union ethash_hash256* difficulty) NOEXCEPT;


/**
* @deprecated Use ethash_verify_against_boundary().
*/
Expand Down
7 changes: 7 additions & 0 deletions include/ethash/ethash.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,13 @@ inline std::error_code verify_final_hash(const hash256& header_hash, const hash2
return ethash_verify_final_hash(&header_hash, &mix_hash, nonce, &boundary);
}

inline std::error_code verify_against_difficulty(const epoch_context& context,
const hash256& header_hash, const hash256& mix_hash, uint64_t nonce,
const hash256& difficulty) noexcept
{
return ethash_verify_against_difficulty(&context, &header_hash, &mix_hash, nonce, &difficulty);
}

inline std::error_code verify_against_boundary(const epoch_context& context,
const hash256& header_hash, const hash256& mix_hash, uint64_t nonce,
const hash256& boundary) noexcept
Expand Down
2 changes: 2 additions & 0 deletions lib/ethash/ethash-internal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ inline bool equal(const hash256& a, const hash256& b) noexcept
(a.word64s[2] == b.word64s[2]) & (a.word64s[3] == b.word64s[3]);
}

bool check_against_difficulty(const hash256& final_hash, const hash256& difficulty) noexcept;

void build_light_cache(hash512 cache[], int num_items, const hash256& seed) noexcept;

hash512 calculate_dataset_item_512(const epoch_context& context, int64_t index) noexcept;
Expand Down
120 changes: 117 additions & 3 deletions lib/ethash/ethash.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,107 @@ search_result search(const epoch_context_full& context, const hash256& header_ha
}
return {};
}


#pragma clang diagnostic ignored "-Wunknown-sanitizers"

struct uint128
{
uint64_t lo;
uint64_t hi;
};

/// Full unsigned multiplication 64 x 64 -> 128.
NO_SANITIZE("unsigned-integer-overflow")
NO_SANITIZE("unsigned-shift-base")
inline uint128 umul(uint64_t x, uint64_t y) noexcept
{
#ifdef __SIZEOF_INT128__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic" // Usage of __int128 triggers a pedantic warning.
using builtin_uint128 = unsigned __int128;
#pragma GCC diagnostic pop

const auto p = builtin_uint128{x} * builtin_uint128{y};
const auto hi = static_cast<uint64_t>(p >> 64);
const auto lo = static_cast<uint64_t>(p);
#else
uint64_t xl = x & 0xffffffff;
uint64_t xh = x >> 32;
uint64_t yl = y & 0xffffffff;
uint64_t yh = y >> 32;

uint64_t t0 = xl * yl;
uint64_t t1 = xh * yl;
uint64_t t2 = xl * yh;
uint64_t t3 = xh * yh;

uint64_t u1 = t1 + (t0 >> 32);
uint64_t u2 = t2 + (u1 & 0xffffffff);

uint64_t lo = (u2 << 32) | (t0 & 0xffffffff);
uint64_t hi = t3 + (u2 >> 32) + (u1 >> 32);
#endif
return {lo, hi};
}

NO_SANITIZE("unsigned-integer-overflow")
bool check_against_difficulty(const hash256& final_hash, const hash256& difficulty) noexcept
{
constexpr size_t num_words = sizeof(hash256) / sizeof(uint64_t);

// Convert difficulty to little-endian array of native 64-bit words.
uint64_t d[num_words];
for (size_t i = 0; i < num_words; ++i)
d[i] = be::uint64(difficulty.word64s[num_words - 1 - i]);

// Convert hash to little-endian array of native 64-bit words.
uint64_t h[num_words];
for (size_t i = 0; i < num_words; ++i)
h[i] = be::uint64(final_hash.word64s[num_words - 1 - i]);

// Compute p = h * d.
uint64_t p[2 * num_words];

{ // First round for d[0]. Fills p[0:4].
uint64_t k = 0;
for (size_t i = 0; i < num_words; ++i)
{
const auto r = umul(h[i], d[0]);
p[i] = r.lo + k;
k = r.hi + (p[i] < k);
}
p[4] = k;
}

// Rounds 1-3. The d[j] is likely 0 so omit the round in such case.
for (size_t j = 1; j < num_words; ++j)
{
uint64_t k = 0;
if (d[j] != 0)
{
for (size_t i = 0; i < num_words; ++i)
{
const auto r = umul(h[i], d[j]);
const auto t = p[j + i] + r.lo;
p[i + j] = t + k;
k = r.hi + (t < r.lo) + (p[j + i] < k);
}
}
p[j + 4] = k; // Always init p[].
}

// Check if p <= 2^256.
// In most cases the p < 2^256 (i.e. top 4 words are zero) should be enough.
const auto high_zero = (p[7] | p[6] | p[5] | p[4]) == 0;
if (high_zero)
return true;

// Lastly, check p == 2^256.
const auto low_zero = (p[3] | p[2] | p[1] | p[0]) == 0;
const auto high_one = (((p[7] | p[6] | p[5]) == 0) & (p[4] == 1)) != 0;
return low_zero && high_one;
}
} // namespace ethash

using namespace ethash;
Expand Down Expand Up @@ -415,12 +516,13 @@ ethash_result ethash_hash(
return {hash_final(seed, mix_hash), mix_hash};
}


ethash_errc ethash_verify_final_hash(const hash256* header_hash, const hash256* mix_hash,
uint64_t nonce, const hash256* boundary) noexcept
{
const hash512 seed = hash_seed(*header_hash, nonce);
return less_equal(hash_final(seed, *mix_hash), *boundary) ? ETHASH_SUCCESS :
ETHASH_INVALID_FINAL_HASH;
return less_equal(hash_final(hash_seed(*header_hash, nonce), *mix_hash), *boundary) ?
ETHASH_SUCCESS :
ETHASH_INVALID_FINAL_HASH;
}

ethash_errc ethash_verify_against_boundary(const epoch_context* context, const hash256* header_hash,
Expand All @@ -434,4 +536,16 @@ ethash_errc ethash_verify_against_boundary(const epoch_context* context, const h
return equal(expected_mix_hash, *mix_hash) ? ETHASH_SUCCESS : ETHASH_INVALID_MIX_HASH;
}

ethash_errc ethash_verify_against_difficulty(const epoch_context* context,
const hash256* header_hash, const hash256* mix_hash, uint64_t nonce,
const hash256* difficulty) noexcept
{
const hash512 seed = hash_seed(*header_hash, nonce);
if (!check_against_difficulty(hash_final(seed, *mix_hash), *difficulty))
return ETHASH_INVALID_FINAL_HASH;

const hash256 expected_mix_hash = hash_kernel(*context, seed, calculate_dataset_item_1024);
return equal(expected_mix_hash, *mix_hash) ? ETHASH_SUCCESS : ETHASH_INVALID_MIX_HASH;
}

} // extern "C"
81 changes: 14 additions & 67 deletions test/fuzzing/difficulty_fuzz.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,99 +4,46 @@

#include "../experimental/difficulty.h"
#include "../unittests/helpers.hpp"
#include <ethash/hash_types.h>
#include "../lib/ethash/ethash-internal.hpp"
#include <cstring>
#include <iostream>

constexpr size_t input_size = sizeof(ethash_hash256);

NO_SANITIZE("unsigned-integer-overflow")
NO_SANITIZE("unsigned-shift-base")
static inline uint64_t umul_add_to(uint64_t* p, uint64_t x, uint64_t y, uint64_t k) noexcept
{
// const auto r = intx::umul(x, y) + a + b;
// return {r[0], r[1]};

// Portable full unsigned multiplication 64 x 64 -> 128.
uint64_t xl = x & 0xffffffff;
uint64_t xh = x >> 32;
uint64_t yl = y & 0xffffffff;
uint64_t yh = y >> 32;

uint64_t t0 = xl * yl;
uint64_t t1 = xh * yl;
uint64_t t2 = xl * yh;
uint64_t t3 = xh * yh;

uint64_t u1 = t1 + (t0 >> 32);
uint64_t u2 = t2 + (u1 & 0xffffffff);

uint64_t plo = (u2 << 32) | (t0 & 0xffffffff);
uint64_t phi = t3 + (u2 >> 32) + (u1 >> 32);

*p += plo;
phi += (*p < plo);

*p += k;
phi += (*p < k);
return phi;
}

NO_SANITIZE("unsigned-integer-overflow")
bool validate(const ethash_hash256& difficulty, const ethash_hash256& boundary) noexcept
{
uint64_t d[4];
for (int i = 0; i < 4; ++i)
d[i] = __builtin_bswap64(difficulty.word64s[4 - 1 - i]);

uint64_t b[4];
for (int i = 0; i < 4; ++i)
b[i] = __builtin_bswap64(boundary.word64s[4 - 1 - i]);

uint64_t p[8]{};
for (size_t j = 0; j < 4; ++j)
{
uint64_t k = 0;
for (size_t i = 0; i < 4; ++i)
k = umul_add_to(&p[i + j], b[i], d[j], k);
p[j + 4] = k;
}

const auto top_zero = (p[7] | p[6] | p[5]) == 0;
const auto overflow = !top_zero | (p[4] != 0);
const auto low_zero = (p[3] | p[2] | p[1] | p[0]) == 0;
const auto high_one = top_zero & (p[4] == 1);

return !overflow | (low_zero & high_one);
}


extern "C" size_t LLVMFuzzerMutate(uint8_t* data, size_t size, size_t max_size);

extern "C" size_t LLVMFuzzerCustomMutator(
uint8_t* data, size_t size, size_t max_size, unsigned int /*seed*/) noexcept
{
if (max_size >= input_size)
if (max_size >= 2 * input_size)
size = 2 * input_size;
else if (max_size >= input_size)
size = input_size;
return LLVMFuzzerMutate(data, size, max_size);
}

extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t data_size) noexcept
{
using namespace ethash;

if (data_size < input_size)
return 0;


ethash_hash256 difficulty;
std::memcpy(difficulty.bytes, data, sizeof(difficulty));

ethash_hash256 boundary = ethash_difficulty_to_boundary(&difficulty);

bool valid = validate(difficulty, boundary);
auto hash = boundary;
if (data_size >= 2 * input_size)
std::memcpy(hash.bytes, data + input_size, sizeof(hash));

const auto check_difficulty = check_against_difficulty(hash, difficulty);
const auto check_boundary = less_equal(hash, boundary);

if (!valid)
if (check_boundary != check_difficulty)
{
std::cerr << "FAILED:\n" << to_hex(difficulty) << "\n" << to_hex(boundary) << "\n";
std::cerr << "FAILED:\n" << to_hex(difficulty) << "\n" << to_hex(hash) << "\n";
__builtin_trap();
}

Expand Down
Loading