Auto merge of #921 - str4d:optimised-equihash, r=daira
Optimise memory usage of Equihash implementation Part of #857.
This commit is contained in:
commit
55bf149df9
|
@ -19,183 +19,214 @@
|
|||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
|
||||
void validate_params(int n, int k)
|
||||
{
|
||||
if (k>=n) {
|
||||
std::cerr << "n must be larger than k\n";
|
||||
throw invalid_params();
|
||||
}
|
||||
if (n % 8 != 0) {
|
||||
std::cerr << "Parameters must satisfy n = 0 mod 8\n";
|
||||
throw invalid_params();
|
||||
}
|
||||
if ((n/(k+1)) % 8 != 0) {
|
||||
std::cerr << "Parameters must satisfy n/(k+1) = 0 mod 8\n";
|
||||
throw invalid_params();
|
||||
}
|
||||
}
|
||||
|
||||
int Equihash::InitialiseState(eh_HashState& base_state)
|
||||
template<unsigned int N, unsigned int K>
|
||||
int Equihash<N,K>::InitialiseState(eh_HashState& base_state)
|
||||
{
|
||||
unsigned int n = N;
|
||||
unsigned int k = K;
|
||||
unsigned char personalization[crypto_generichash_blake2b_PERSONALBYTES] = {};
|
||||
memcpy(personalization, "ZcashPOW", 8);
|
||||
memcpy(personalization+8, &n, 4);
|
||||
memcpy(personalization+12, &k, 4);
|
||||
return crypto_generichash_blake2b_init_salt_personal(&base_state,
|
||||
NULL, 0, // No key.
|
||||
n/8,
|
||||
N/8,
|
||||
NULL, // No salt.
|
||||
personalization);
|
||||
}
|
||||
|
||||
StepRow::StepRow(unsigned int n, const eh_HashState& base_state, eh_index i) :
|
||||
hash {new unsigned char[n/8]},
|
||||
len {n/8},
|
||||
indices {i}
|
||||
void EhIndexToArray(const eh_index i, unsigned char* array)
|
||||
{
|
||||
assert(sizeof(eh_index) == 4);
|
||||
array[0] = (i >> 24) & 0xFF;
|
||||
array[1] = (i >> 16) & 0xFF;
|
||||
array[2] = (i >> 8) & 0xFF;
|
||||
array[3] = i & 0xFF;
|
||||
}
|
||||
|
||||
eh_index ArrayToEhIndex(const unsigned char* array)
|
||||
{
|
||||
assert(sizeof(eh_index) == 4);
|
||||
eh_index ret {array[0]};
|
||||
ret <<= 8;
|
||||
ret |= array[1];
|
||||
ret <<= 8;
|
||||
ret |= array[2];
|
||||
ret <<= 8;
|
||||
ret |= array[3];
|
||||
return ret;
|
||||
}
|
||||
|
||||
eh_trunc TruncateIndex(const eh_index i, const unsigned int ilen)
|
||||
{
|
||||
// Truncate to 8 bits
|
||||
assert(sizeof(eh_trunc) == 1);
|
||||
return (i >> (ilen - 8)) & 0xff;
|
||||
}
|
||||
|
||||
eh_index UntruncateIndex(const eh_trunc t, const eh_index r, const unsigned int ilen)
|
||||
{
|
||||
eh_index i{t};
|
||||
return (i << (ilen - 8)) | r;
|
||||
}
|
||||
|
||||
template<size_t WIDTH>
|
||||
StepRow<WIDTH>::StepRow(unsigned int n, const eh_HashState& base_state, eh_index i)
|
||||
{
|
||||
eh_HashState state;
|
||||
state = base_state;
|
||||
crypto_generichash_blake2b_update(&state, (unsigned char*) &i, sizeof(eh_index));
|
||||
crypto_generichash_blake2b_final(&state, hash, n/8);
|
||||
|
||||
assert(indices.size() == 1);
|
||||
}
|
||||
|
||||
StepRow::~StepRow()
|
||||
template<size_t WIDTH> template<size_t W>
|
||||
StepRow<WIDTH>::StepRow(const StepRow<W>& a)
|
||||
{
|
||||
delete[] hash;
|
||||
assert(W <= WIDTH);
|
||||
std::copy(a.hash, a.hash+W, hash);
|
||||
}
|
||||
|
||||
StepRow::StepRow(const StepRow& a) :
|
||||
hash {new unsigned char[a.len]},
|
||||
len {a.len},
|
||||
indices(a.indices)
|
||||
template<size_t WIDTH>
|
||||
FullStepRow<WIDTH>::FullStepRow(unsigned int n, const eh_HashState& base_state, eh_index i) :
|
||||
StepRow<WIDTH> {n, base_state, i}
|
||||
{
|
||||
for (int i = 0; i < len; i++)
|
||||
hash[i] = a.hash[i];
|
||||
EhIndexToArray(i, hash+(n/8));
|
||||
}
|
||||
|
||||
StepRow& StepRow::operator=(const StepRow& a)
|
||||
template<size_t WIDTH> template<size_t W>
|
||||
FullStepRow<WIDTH>::FullStepRow(const FullStepRow<W>& a, const FullStepRow<W>& b, size_t len, size_t lenIndices, int trim) :
|
||||
StepRow<WIDTH> {a}
|
||||
{
|
||||
unsigned char* p = new unsigned char[a.len];
|
||||
for (int i = 0; i < a.len; i++)
|
||||
p[i] = a.hash[i];
|
||||
delete[] hash;
|
||||
hash = p;
|
||||
len = a.len;
|
||||
indices = a.indices;
|
||||
assert(len+lenIndices <= W);
|
||||
assert(len-trim+(2*lenIndices) <= WIDTH);
|
||||
for (int i = trim; i < len; i++)
|
||||
hash[i-trim] = a.hash[i] ^ b.hash[i];
|
||||
if (a.IndicesBefore(b, len)) {
|
||||
std::copy(a.hash+len, a.hash+len+lenIndices, hash+len-trim);
|
||||
std::copy(b.hash+len, b.hash+len+lenIndices, hash+len-trim+lenIndices);
|
||||
} else {
|
||||
std::copy(b.hash+len, b.hash+len+lenIndices, hash+len-trim);
|
||||
std::copy(a.hash+len, a.hash+len+lenIndices, hash+len-trim+lenIndices);
|
||||
}
|
||||
}
|
||||
|
||||
template<size_t WIDTH>
|
||||
FullStepRow<WIDTH>& FullStepRow<WIDTH>::operator=(const FullStepRow<WIDTH>& a)
|
||||
{
|
||||
std::copy(a.hash, a.hash+WIDTH, hash);
|
||||
return *this;
|
||||
}
|
||||
|
||||
StepRow& StepRow::operator^=(const StepRow& a)
|
||||
template<size_t WIDTH>
|
||||
bool StepRow<WIDTH>::IsZero(size_t len)
|
||||
{
|
||||
if (a.len != len) {
|
||||
throw std::invalid_argument("Hash length differs");
|
||||
}
|
||||
if (a.indices.size() != indices.size()) {
|
||||
throw std::invalid_argument("Number of indices differs");
|
||||
}
|
||||
unsigned char* p = new unsigned char[len];
|
||||
for (int i = 0; i < len; i++)
|
||||
p[i] = hash[i] ^ a.hash[i];
|
||||
delete[] hash;
|
||||
hash = p;
|
||||
indices.reserve(indices.size() + a.indices.size());
|
||||
indices.insert(indices.end(), a.indices.begin(), a.indices.end());
|
||||
return *this;
|
||||
}
|
||||
|
||||
void StepRow::TrimHash(int l)
|
||||
{
|
||||
unsigned char* p = new unsigned char[len-l];
|
||||
for (int i = 0; i < len-l; i++)
|
||||
p[i] = hash[i+l];
|
||||
delete[] hash;
|
||||
hash = p;
|
||||
len -= l;
|
||||
}
|
||||
|
||||
bool StepRow::IsZero()
|
||||
{
|
||||
char res = 0;
|
||||
for (int i = 0; i < len; i++)
|
||||
res |= hash[i];
|
||||
return res == 0;
|
||||
}
|
||||
|
||||
bool HasCollision(StepRow& a, StepRow& b, int l)
|
||||
{
|
||||
bool res = true;
|
||||
for (int j = 0; j < l; j++)
|
||||
res &= a.hash[j] == b.hash[j];
|
||||
return res;
|
||||
}
|
||||
|
||||
// Checks if the intersection of a.indices and b.indices is empty
|
||||
bool DistinctIndices(const StepRow& a, const StepRow& b)
|
||||
{
|
||||
std::vector<eh_index> aSrt(a.indices);
|
||||
std::vector<eh_index> bSrt(b.indices);
|
||||
|
||||
std::sort(aSrt.begin(), aSrt.end());
|
||||
std::sort(bSrt.begin(), bSrt.end());
|
||||
|
||||
unsigned int i = 0;
|
||||
for (unsigned int j = 0; j < bSrt.size(); j++) {
|
||||
while (aSrt[i] < bSrt[j]) {
|
||||
i++;
|
||||
if (i == aSrt.size()) { return true; }
|
||||
}
|
||||
assert(aSrt[i] >= bSrt[j]);
|
||||
if (aSrt[i] == bSrt[j]) { return false; }
|
||||
// This doesn't need to be constant time.
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (hash[i] != 0)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Equihash::Equihash(unsigned int n, unsigned int k) :
|
||||
n(n), k(k)
|
||||
template<size_t WIDTH>
|
||||
std::vector<eh_index> FullStepRow<WIDTH>::GetIndices(size_t len, size_t lenIndices) const
|
||||
{
|
||||
validate_params(n, k);
|
||||
std::vector<eh_index> ret;
|
||||
for (int i = 0; i < lenIndices; i += sizeof(eh_index)) {
|
||||
ret.push_back(ArrayToEhIndex(hash+len+i));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::set<std::vector<eh_index>> Equihash::BasicSolve(const eh_HashState& base_state)
|
||||
template<size_t WIDTH>
|
||||
bool HasCollision(StepRow<WIDTH>& a, StepRow<WIDTH>& b, int l)
|
||||
{
|
||||
assert(CollisionBitLength() + 1 < 8*sizeof(eh_index));
|
||||
eh_index init_size { ((eh_index) 1) << (CollisionBitLength() + 1) };
|
||||
// This doesn't need to be constant time.
|
||||
for (int j = 0; j < l; j++) {
|
||||
if (a.hash[j] != b.hash[j])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template<size_t WIDTH>
|
||||
TruncatedStepRow<WIDTH>::TruncatedStepRow(unsigned int n, const eh_HashState& base_state, eh_index i, unsigned int ilen) :
|
||||
StepRow<WIDTH> {n, base_state, i}
|
||||
{
|
||||
hash[n/8] = TruncateIndex(i, ilen);
|
||||
}
|
||||
|
||||
template<size_t WIDTH> template<size_t W>
|
||||
TruncatedStepRow<WIDTH>::TruncatedStepRow(const TruncatedStepRow<W>& a, const TruncatedStepRow<W>& b, size_t len, size_t lenIndices, int trim) :
|
||||
StepRow<WIDTH> {a}
|
||||
{
|
||||
assert(len+lenIndices <= W);
|
||||
assert(len-trim+(2*lenIndices) <= WIDTH);
|
||||
for (int i = trim; i < len; i++)
|
||||
hash[i-trim] = a.hash[i] ^ b.hash[i];
|
||||
if (a.IndicesBefore(b, len, lenIndices)) {
|
||||
std::copy(a.hash+len, a.hash+len+lenIndices, hash+len-trim);
|
||||
std::copy(b.hash+len, b.hash+len+lenIndices, hash+len-trim+lenIndices);
|
||||
} else {
|
||||
std::copy(b.hash+len, b.hash+len+lenIndices, hash+len-trim);
|
||||
std::copy(a.hash+len, a.hash+len+lenIndices, hash+len-trim+lenIndices);
|
||||
}
|
||||
}
|
||||
|
||||
template<size_t WIDTH>
|
||||
TruncatedStepRow<WIDTH>& TruncatedStepRow<WIDTH>::operator=(const TruncatedStepRow<WIDTH>& a)
|
||||
{
|
||||
std::copy(a.hash, a.hash+WIDTH, hash);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<size_t WIDTH>
|
||||
eh_trunc* TruncatedStepRow<WIDTH>::GetTruncatedIndices(size_t len, size_t lenIndices) const
|
||||
{
|
||||
eh_trunc* p = new eh_trunc[lenIndices];
|
||||
std::copy(hash+len, hash+len+lenIndices, p);
|
||||
return p;
|
||||
}
|
||||
|
||||
template<unsigned int N, unsigned int K>
|
||||
std::set<std::vector<eh_index>> Equihash<N,K>::BasicSolve(const eh_HashState& base_state)
|
||||
{
|
||||
eh_index init_size { 1 << (CollisionBitLength + 1) };
|
||||
|
||||
// 1) Generate first list
|
||||
LogPrint("pow", "Generating first list\n");
|
||||
std::vector<StepRow> X;
|
||||
size_t hashLen = N/8;
|
||||
size_t lenIndices = sizeof(eh_index);
|
||||
std::vector<FullStepRow<FullWidth>> X;
|
||||
X.reserve(init_size);
|
||||
for (eh_index i = 0; i < init_size; i++) {
|
||||
X.emplace_back(n, base_state, i);
|
||||
X.emplace_back(N, base_state, i);
|
||||
}
|
||||
|
||||
// 3) Repeat step 2 until 2n/(k+1) bits remain
|
||||
for (int r = 1; r < k && X.size() > 0; r++) {
|
||||
for (int r = 1; r < K && X.size() > 0; r++) {
|
||||
LogPrint("pow", "Round %d:\n", r);
|
||||
// 2a) Sort the list
|
||||
LogPrint("pow", "- Sorting list\n");
|
||||
std::sort(X.begin(), X.end());
|
||||
std::sort(X.begin(), X.end(), CompareSR(hashLen));
|
||||
|
||||
LogPrint("pow", "- Finding collisions\n");
|
||||
int i = 0;
|
||||
int posFree = 0;
|
||||
std::vector<StepRow> Xc;
|
||||
std::vector<FullStepRow<FullWidth>> Xc;
|
||||
while (i < X.size() - 1) {
|
||||
// 2b) Find next set of unordered pairs with collisions on the next n/(k+1) bits
|
||||
int j = 1;
|
||||
while (i+j < X.size() &&
|
||||
HasCollision(X[i], X[i+j], CollisionByteLength())) {
|
||||
HasCollision(X[i], X[i+j], CollisionByteLength)) {
|
||||
j++;
|
||||
}
|
||||
|
||||
// 2c) Calculate tuples (X_i ^ X_j, (i, j))
|
||||
for (int l = 0; l < j - 1; l++) {
|
||||
for (int m = l + 1; m < j; m++) {
|
||||
if (DistinctIndices(X[i+l], X[i+m])) {
|
||||
Xc.push_back(X[i+l] ^ X[i+m]);
|
||||
Xc.back().TrimHash(CollisionByteLength());
|
||||
if (DistinctIndices(X[i+l], X[i+m], hashLen, lenIndices)) {
|
||||
Xc.emplace_back(X[i+l], X[i+m], hashLen, lenIndices, CollisionByteLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -223,6 +254,9 @@ std::set<std::vector<eh_index>> Equihash::BasicSolve(const eh_HashState& base_st
|
|||
X.erase(X.begin()+posFree, X.end());
|
||||
X.shrink_to_fit();
|
||||
}
|
||||
|
||||
hashLen -= CollisionByteLength;
|
||||
lenIndices *= 2;
|
||||
}
|
||||
|
||||
// k+1) Find a collision on last 2n(k+1) bits
|
||||
|
@ -230,12 +264,12 @@ std::set<std::vector<eh_index>> Equihash::BasicSolve(const eh_HashState& base_st
|
|||
std::set<std::vector<eh_index>> solns;
|
||||
if (X.size() > 1) {
|
||||
LogPrint("pow", "- Sorting list\n");
|
||||
std::sort(X.begin(), X.end());
|
||||
std::sort(X.begin(), X.end(), CompareSR(hashLen));
|
||||
LogPrint("pow", "- Finding collisions\n");
|
||||
for (int i = 0; i < X.size() - 1; i++) {
|
||||
StepRow res = X[i] ^ X[i+1];
|
||||
if (res.IsZero() && DistinctIndices(X[i], X[i+1])) {
|
||||
solns.insert(res.GetSolution());
|
||||
FullStepRow<FinalFullWidth> res(X[i], X[i+1], hashLen, lenIndices, 0);
|
||||
if (res.IsZero(hashLen) && DistinctIndices(X[i], X[i+1], hashLen, lenIndices)) {
|
||||
solns.insert(res.GetIndices(hashLen, 2*lenIndices));
|
||||
}
|
||||
}
|
||||
} else
|
||||
|
@ -244,43 +278,273 @@ std::set<std::vector<eh_index>> Equihash::BasicSolve(const eh_HashState& base_st
|
|||
return solns;
|
||||
}
|
||||
|
||||
bool Equihash::IsValidSolution(const eh_HashState& base_state, std::vector<eh_index> soln)
|
||||
template<size_t WIDTH>
|
||||
void CollideBranches(std::vector<FullStepRow<WIDTH>>& X, const size_t hlen, const size_t lenIndices, const unsigned int clen, const unsigned int ilen, const eh_trunc lt, const eh_trunc rt)
|
||||
{
|
||||
eh_index soln_size { 1u << k };
|
||||
int i = 0;
|
||||
int posFree = 0;
|
||||
std::vector<FullStepRow<WIDTH>> Xc;
|
||||
while (i < X.size() - 1) {
|
||||
// 2b) Find next set of unordered pairs with collisions on the next n/(k+1) bits
|
||||
int j = 1;
|
||||
while (i+j < X.size() &&
|
||||
HasCollision(X[i], X[i+j], clen)) {
|
||||
j++;
|
||||
}
|
||||
|
||||
// 2c) Calculate tuples (X_i ^ X_j, (i, j))
|
||||
for (int l = 0; l < j - 1; l++) {
|
||||
for (int m = l + 1; m < j; m++) {
|
||||
if (DistinctIndices(X[i+l], X[i+m], hlen, lenIndices)) {
|
||||
if (IsValidBranch(X[i+l], hlen, ilen, lt) && IsValidBranch(X[i+m], hlen, ilen, rt)) {
|
||||
Xc.emplace_back(X[i+l], X[i+m], hlen, lenIndices, clen);
|
||||
} else if (IsValidBranch(X[i+m], hlen, ilen, lt) && IsValidBranch(X[i+l], hlen, ilen, rt)) {
|
||||
Xc.emplace_back(X[i+m], X[i+l], hlen, lenIndices, clen);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2d) Store tuples on the table in-place if possible
|
||||
while (posFree < i+j && Xc.size() > 0) {
|
||||
X[posFree++] = Xc.back();
|
||||
Xc.pop_back();
|
||||
}
|
||||
|
||||
i += j;
|
||||
}
|
||||
|
||||
// 2e) Handle edge case where final table entry has no collision
|
||||
while (posFree < X.size() && Xc.size() > 0) {
|
||||
X[posFree++] = Xc.back();
|
||||
Xc.pop_back();
|
||||
}
|
||||
|
||||
if (Xc.size() > 0) {
|
||||
// 2f) Add overflow to end of table
|
||||
X.insert(X.end(), Xc.begin(), Xc.end());
|
||||
} else if (posFree < X.size()) {
|
||||
// 2g) Remove empty space at the end
|
||||
X.erase(X.begin()+posFree, X.end());
|
||||
X.shrink_to_fit();
|
||||
}
|
||||
}
|
||||
|
||||
template<unsigned int N, unsigned int K>
|
||||
std::set<std::vector<eh_index>> Equihash<N,K>::OptimisedSolve(const eh_HashState& base_state)
|
||||
{
|
||||
eh_index init_size { 1 << (CollisionBitLength + 1) };
|
||||
|
||||
// First run the algorithm with truncated indices
|
||||
|
||||
eh_index soln_size { 1 << K };
|
||||
// Each element of partialSolns is dynamically allocated in a call to
|
||||
// GetTruncatedIndices(), and freed at the end of this function.
|
||||
std::vector<eh_trunc*> partialSolns;
|
||||
{
|
||||
|
||||
// 1) Generate first list
|
||||
LogPrint("pow", "Generating first list\n");
|
||||
size_t hashLen = N/8;
|
||||
size_t lenIndices = sizeof(eh_trunc);
|
||||
std::vector<TruncatedStepRow<TruncatedWidth>> Xt;
|
||||
Xt.reserve(init_size);
|
||||
for (eh_index i = 0; i < init_size; i++) {
|
||||
Xt.emplace_back(N, base_state, i, CollisionBitLength + 1);
|
||||
}
|
||||
|
||||
// 3) Repeat step 2 until 2n/(k+1) bits remain
|
||||
for (int r = 1; r < K && Xt.size() > 0; r++) {
|
||||
LogPrint("pow", "Round %d:\n", r);
|
||||
// 2a) Sort the list
|
||||
LogPrint("pow", "- Sorting list\n");
|
||||
std::sort(Xt.begin(), Xt.end(), CompareSR(hashLen));
|
||||
|
||||
LogPrint("pow", "- Finding collisions\n");
|
||||
int i = 0;
|
||||
int posFree = 0;
|
||||
std::vector<TruncatedStepRow<TruncatedWidth>> Xc;
|
||||
while (i < Xt.size() - 1) {
|
||||
// 2b) Find next set of unordered pairs with collisions on the next n/(k+1) bits
|
||||
int j = 1;
|
||||
while (i+j < Xt.size() &&
|
||||
HasCollision(Xt[i], Xt[i+j], CollisionByteLength)) {
|
||||
j++;
|
||||
}
|
||||
|
||||
// 2c) Calculate tuples (X_i ^ X_j, (i, j))
|
||||
for (int l = 0; l < j - 1; l++) {
|
||||
for (int m = l + 1; m < j; m++) {
|
||||
// We truncated, so don't check for distinct indices here
|
||||
Xc.emplace_back(Xt[i+l], Xt[i+m], hashLen, lenIndices, CollisionByteLength);
|
||||
}
|
||||
}
|
||||
|
||||
// 2d) Store tuples on the table in-place if possible
|
||||
while (posFree < i+j && Xc.size() > 0) {
|
||||
Xt[posFree++] = Xc.back();
|
||||
Xc.pop_back();
|
||||
}
|
||||
|
||||
i += j;
|
||||
}
|
||||
|
||||
// 2e) Handle edge case where final table entry has no collision
|
||||
while (posFree < Xt.size() && Xc.size() > 0) {
|
||||
Xt[posFree++] = Xc.back();
|
||||
Xc.pop_back();
|
||||
}
|
||||
|
||||
if (Xc.size() > 0) {
|
||||
// 2f) Add overflow to end of table
|
||||
Xt.insert(Xt.end(), Xc.begin(), Xc.end());
|
||||
} else if (posFree < Xt.size()) {
|
||||
// 2g) Remove empty space at the end
|
||||
Xt.erase(Xt.begin()+posFree, Xt.end());
|
||||
Xt.shrink_to_fit();
|
||||
}
|
||||
|
||||
hashLen -= CollisionByteLength;
|
||||
lenIndices *= 2;
|
||||
}
|
||||
|
||||
// k+1) Find a collision on last 2n(k+1) bits
|
||||
LogPrint("pow", "Final round:\n");
|
||||
if (Xt.size() > 1) {
|
||||
LogPrint("pow", "- Sorting list\n");
|
||||
std::sort(Xt.begin(), Xt.end(), CompareSR(hashLen));
|
||||
LogPrint("pow", "- Finding collisions\n");
|
||||
for (int i = 0; i < Xt.size() - 1; i++) {
|
||||
TruncatedStepRow<FinalTruncatedWidth> res(Xt[i], Xt[i+1], hashLen, lenIndices, 0);
|
||||
if (res.IsZero(hashLen)) {
|
||||
partialSolns.push_back(res.GetTruncatedIndices(hashLen, 2*lenIndices));
|
||||
}
|
||||
}
|
||||
} else
|
||||
LogPrint("pow", "- List is empty\n");
|
||||
|
||||
} // Ensure Xt goes out of scope and is destroyed
|
||||
|
||||
LogPrint("pow", "Found %d partial solutions\n", partialSolns.size());
|
||||
|
||||
// Now for each solution run the algorithm again to recreate the indices
|
||||
LogPrint("pow", "Culling solutions\n");
|
||||
std::set<std::vector<eh_index>> solns;
|
||||
eh_index recreate_size { UntruncateIndex(1, 0, CollisionBitLength + 1) };
|
||||
int invalidCount = 0;
|
||||
for (eh_trunc* partialSoln : partialSolns) {
|
||||
// 1) Generate first list of possibilities
|
||||
size_t hashLen = N/8;
|
||||
size_t lenIndices = sizeof(eh_index);
|
||||
std::vector<std::vector<FullStepRow<FinalFullWidth>>> X;
|
||||
X.reserve(soln_size);
|
||||
for (eh_index i = 0; i < soln_size; i++) {
|
||||
std::vector<FullStepRow<FinalFullWidth>> ic;
|
||||
ic.reserve(recreate_size);
|
||||
for (eh_index j = 0; j < recreate_size; j++) {
|
||||
eh_index newIndex { UntruncateIndex(partialSoln[i], j, CollisionBitLength + 1) };
|
||||
ic.emplace_back(N, base_state, newIndex);
|
||||
}
|
||||
X.push_back(ic);
|
||||
}
|
||||
|
||||
// 3) Repeat step 2 for each level of the tree
|
||||
for (int r = 0; X.size() > 1; r++) {
|
||||
std::vector<std::vector<FullStepRow<FinalFullWidth>>> Xc;
|
||||
Xc.reserve(X.size()/2);
|
||||
|
||||
// 2a) For each pair of lists:
|
||||
for (int v = 0; v < X.size(); v += 2) {
|
||||
// 2b) Merge the lists
|
||||
std::vector<FullStepRow<FinalFullWidth>> ic(X[v]);
|
||||
ic.reserve(X[v].size() + X[v+1].size());
|
||||
ic.insert(ic.end(), X[v+1].begin(), X[v+1].end());
|
||||
std::sort(ic.begin(), ic.end(), CompareSR(hashLen));
|
||||
CollideBranches(ic, hashLen, lenIndices, CollisionByteLength, CollisionBitLength + 1, partialSoln[(1<<r)*v], partialSoln[(1<<r)*(v+1)]);
|
||||
|
||||
// 2v) Check if this has become an invalid solution
|
||||
if (ic.size() == 0)
|
||||
goto invalidsolution;
|
||||
|
||||
Xc.push_back(ic);
|
||||
}
|
||||
|
||||
X = Xc;
|
||||
hashLen -= CollisionByteLength;
|
||||
lenIndices *= 2;
|
||||
}
|
||||
|
||||
// We are at the top of the tree
|
||||
assert(X.size() == 1);
|
||||
for (FullStepRow<FinalFullWidth> row : X[0]) {
|
||||
solns.insert(row.GetIndices(hashLen, lenIndices));
|
||||
}
|
||||
goto deletesolution;
|
||||
|
||||
invalidsolution:
|
||||
invalidCount++;
|
||||
|
||||
deletesolution:
|
||||
delete[] partialSoln;
|
||||
}
|
||||
LogPrint("pow", "- Number of invalid solutions found: %d\n", invalidCount);
|
||||
|
||||
return solns;
|
||||
}
|
||||
|
||||
template<unsigned int N, unsigned int K>
|
||||
bool Equihash<N,K>::IsValidSolution(const eh_HashState& base_state, std::vector<eh_index> soln)
|
||||
{
|
||||
eh_index soln_size { 1u << K };
|
||||
if (soln.size() != soln_size) {
|
||||
LogPrint("pow", "Invalid solution size: %d\n", soln.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<StepRow> X;
|
||||
std::vector<FullStepRow<FinalFullWidth>> X;
|
||||
X.reserve(soln_size);
|
||||
for (eh_index i : soln) {
|
||||
X.emplace_back(n, base_state, i);
|
||||
X.emplace_back(N, base_state, i);
|
||||
}
|
||||
|
||||
size_t hashLen = N/8;
|
||||
size_t lenIndices = sizeof(eh_index);
|
||||
while (X.size() > 1) {
|
||||
std::vector<StepRow> Xc;
|
||||
std::vector<FullStepRow<FinalFullWidth>> Xc;
|
||||
for (int i = 0; i < X.size(); i += 2) {
|
||||
if (!HasCollision(X[i], X[i+1], CollisionByteLength())) {
|
||||
if (!HasCollision(X[i], X[i+1], CollisionByteLength)) {
|
||||
LogPrint("pow", "Invalid solution: invalid collision length between StepRows\n");
|
||||
LogPrint("pow", "X[i] = %s\n", X[i].GetHex());
|
||||
LogPrint("pow", "X[i+1] = %s\n", X[i+1].GetHex());
|
||||
LogPrint("pow", "X[i] = %s\n", X[i].GetHex(hashLen));
|
||||
LogPrint("pow", "X[i+1] = %s\n", X[i+1].GetHex(hashLen));
|
||||
return false;
|
||||
}
|
||||
if (X[i+1].IndicesBefore(X[i])) {
|
||||
if (X[i+1].IndicesBefore(X[i], hashLen)) {
|
||||
return false;
|
||||
LogPrint("pow", "Invalid solution: Index tree incorrectly ordered\n");
|
||||
}
|
||||
if (!DistinctIndices(X[i], X[i+1])) {
|
||||
if (!DistinctIndices(X[i], X[i+1], hashLen, lenIndices)) {
|
||||
LogPrint("pow", "Invalid solution: duplicate indices\n");
|
||||
return false;
|
||||
}
|
||||
Xc.push_back(X[i] ^ X[i+1]);
|
||||
Xc.back().TrimHash(CollisionByteLength());
|
||||
Xc.emplace_back(X[i], X[i+1], hashLen, lenIndices, CollisionByteLength);
|
||||
}
|
||||
X = Xc;
|
||||
hashLen -= CollisionByteLength;
|
||||
lenIndices *= 2;
|
||||
}
|
||||
|
||||
assert(X.size() == 1);
|
||||
return X[0].IsZero();
|
||||
return X[0].IsZero(hashLen);
|
||||
}
|
||||
|
||||
// Explicit instantiations for Equihash<96,5>
|
||||
template int Equihash<96,5>::InitialiseState(eh_HashState& base_state);
|
||||
template std::set<std::vector<eh_index>> Equihash<96,5>::BasicSolve(const eh_HashState& base_state);
|
||||
template std::set<std::vector<eh_index>> Equihash<96,5>::OptimisedSolve(const eh_HashState& base_state);
|
||||
template bool Equihash<96,5>::IsValidSolution(const eh_HashState& base_state, std::vector<eh_index> soln);
|
||||
|
||||
// Explicit instantiations for Equihash<48,5>
|
||||
template int Equihash<48,5>::InitialiseState(eh_HashState& base_state);
|
||||
template std::set<std::vector<eh_index>> Equihash<48,5>::BasicSolve(const eh_HashState& base_state);
|
||||
template std::set<std::vector<eh_index>> Equihash<48,5>::OptimisedSolve(const eh_HashState& base_state);
|
||||
template bool Equihash<48,5>::IsValidSolution(const eh_HashState& base_state, std::vector<eh_index> soln);
|
||||
|
|
|
@ -15,61 +15,163 @@
|
|||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/static_assert.hpp>
|
||||
|
||||
typedef crypto_generichash_blake2b_state eh_HashState;
|
||||
typedef uint32_t eh_index;
|
||||
typedef uint8_t eh_trunc;
|
||||
|
||||
struct invalid_params { };
|
||||
eh_index ArrayToEhIndex(const unsigned char* array);
|
||||
eh_trunc TruncateIndex(const eh_index i, const unsigned int ilen);
|
||||
|
||||
template<size_t WIDTH>
|
||||
class StepRow
|
||||
{
|
||||
private:
|
||||
unsigned char* hash;
|
||||
unsigned int len;
|
||||
std::vector<eh_index> indices;
|
||||
template<size_t W>
|
||||
friend class StepRow;
|
||||
friend class CompareSR;
|
||||
|
||||
protected:
|
||||
unsigned char hash[WIDTH];
|
||||
|
||||
public:
|
||||
StepRow(unsigned int n, const eh_HashState& base_state, eh_index i);
|
||||
~StepRow();
|
||||
~StepRow() { }
|
||||
|
||||
StepRow(const StepRow& a);
|
||||
StepRow& operator=(const StepRow& a);
|
||||
StepRow& operator^=(const StepRow& a);
|
||||
template<size_t W>
|
||||
StepRow(const StepRow<W>& a);
|
||||
|
||||
void TrimHash(int l);
|
||||
bool IsZero();
|
||||
bool IndicesBefore(const StepRow& a) { return indices[0] < a.indices[0]; }
|
||||
std::vector<eh_index> GetSolution() { return std::vector<eh_index>(indices); }
|
||||
std::string GetHex() { return HexStr(hash, hash+len); }
|
||||
bool IsZero(size_t len);
|
||||
std::string GetHex(size_t len) { return HexStr(hash, hash+len); }
|
||||
|
||||
friend inline const StepRow operator^(const StepRow& a, const StepRow& b) {
|
||||
if (a.indices[0] < b.indices[0]) { return StepRow(a) ^= b; }
|
||||
else { return StepRow(b) ^= a; }
|
||||
}
|
||||
friend inline bool operator==(const StepRow& a, const StepRow& b) { return memcmp(a.hash, b.hash, a.len) == 0; }
|
||||
friend inline bool operator<(const StepRow& a, const StepRow& b) { return memcmp(a.hash, b.hash, a.len) < 0; }
|
||||
|
||||
friend bool HasCollision(StepRow& a, StepRow& b, int l);
|
||||
friend bool DistinctIndices(const StepRow& a, const StepRow& b);
|
||||
template<size_t W>
|
||||
friend bool HasCollision(StepRow<W>& a, StepRow<W>& b, int l);
|
||||
};
|
||||
|
||||
bool HasCollision(StepRow& a, StepRow& b, int l);
|
||||
bool DistinctIndices(const StepRow& a, const StepRow& b);
|
||||
class CompareSR
|
||||
{
|
||||
private:
|
||||
size_t len;
|
||||
|
||||
public:
|
||||
CompareSR(size_t l) : len {l} { }
|
||||
|
||||
template<size_t W>
|
||||
inline bool operator()(const StepRow<W>& a, const StepRow<W>& b) { return memcmp(a.hash, b.hash, len) < 0; }
|
||||
};
|
||||
|
||||
template<size_t WIDTH>
|
||||
bool HasCollision(StepRow<WIDTH>& a, StepRow<WIDTH>& b, int l);
|
||||
|
||||
template<size_t WIDTH>
|
||||
class FullStepRow : public StepRow<WIDTH>
|
||||
{
|
||||
template<size_t W>
|
||||
friend class FullStepRow;
|
||||
|
||||
using StepRow<WIDTH>::hash;
|
||||
|
||||
public:
|
||||
FullStepRow(unsigned int n, const eh_HashState& base_state, eh_index i);
|
||||
~FullStepRow() { }
|
||||
|
||||
FullStepRow(const FullStepRow<WIDTH>& a) : StepRow<WIDTH> {a} { }
|
||||
template<size_t W>
|
||||
FullStepRow(const FullStepRow<W>& a, const FullStepRow<W>& b, size_t len, size_t lenIndices, int trim);
|
||||
FullStepRow& operator=(const FullStepRow<WIDTH>& a);
|
||||
|
||||
inline bool IndicesBefore(const FullStepRow<WIDTH>& a, size_t len) const { return ArrayToEhIndex(hash+len) < ArrayToEhIndex(a.hash+len); }
|
||||
std::vector<eh_index> GetIndices(size_t len, size_t lenIndices) const;
|
||||
|
||||
template<size_t W>
|
||||
friend bool IsValidBranch(const FullStepRow<W>& a, const size_t len, const unsigned int ilen, const eh_trunc t);
|
||||
};
|
||||
|
||||
template<size_t WIDTH>
|
||||
class TruncatedStepRow : public StepRow<WIDTH>
|
||||
{
|
||||
template<size_t W>
|
||||
friend class TruncatedStepRow;
|
||||
|
||||
using StepRow<WIDTH>::hash;
|
||||
|
||||
public:
|
||||
TruncatedStepRow(unsigned int n, const eh_HashState& base_state, eh_index i, unsigned int ilen);
|
||||
~TruncatedStepRow() { }
|
||||
|
||||
TruncatedStepRow(const TruncatedStepRow<WIDTH>& a) : StepRow<WIDTH> {a} { }
|
||||
template<size_t W>
|
||||
TruncatedStepRow(const TruncatedStepRow<W>& a, const TruncatedStepRow<W>& b, size_t len, size_t lenIndices, int trim);
|
||||
TruncatedStepRow& operator=(const TruncatedStepRow<WIDTH>& a);
|
||||
|
||||
inline bool IndicesBefore(const TruncatedStepRow<WIDTH>& a, size_t len, size_t lenIndices) const { return memcmp(hash+len, a.hash+len, lenIndices) < 0; }
|
||||
eh_trunc* GetTruncatedIndices(size_t len, size_t lenIndices) const;
|
||||
};
|
||||
|
||||
template<unsigned int N, unsigned int K>
|
||||
class Equihash
|
||||
{
|
||||
private:
|
||||
unsigned int n;
|
||||
unsigned int k;
|
||||
BOOST_STATIC_ASSERT(K < N);
|
||||
BOOST_STATIC_ASSERT(N % 8 == 0);
|
||||
BOOST_STATIC_ASSERT((N/(K+1)) % 8 == 0);
|
||||
BOOST_STATIC_ASSERT((N/(K+1)) + 1 < 8*sizeof(eh_index));
|
||||
|
||||
public:
|
||||
Equihash(unsigned int n, unsigned int k);
|
||||
enum { CollisionBitLength=N/(K+1) };
|
||||
enum { CollisionByteLength=CollisionBitLength/8 };
|
||||
enum : size_t { FullWidth=2*CollisionByteLength+sizeof(eh_index)*(1 << (K-1)) };
|
||||
enum : size_t { FinalFullWidth=2*CollisionByteLength+sizeof(eh_index)*(1 << (K)) };
|
||||
enum : size_t { TruncatedWidth=2*CollisionByteLength+sizeof(eh_trunc)*(1 << (K-1)) };
|
||||
enum : size_t { FinalTruncatedWidth=2*CollisionByteLength+sizeof(eh_trunc)*(1 << (K)) };
|
||||
|
||||
inline unsigned int CollisionBitLength() { return n/(k+1); }
|
||||
inline unsigned int CollisionByteLength() { return CollisionBitLength()/8; }
|
||||
Equihash() { }
|
||||
|
||||
int InitialiseState(eh_HashState& base_state);
|
||||
std::set<std::vector<eh_index>> BasicSolve(const eh_HashState& base_state);
|
||||
std::set<std::vector<eh_index>> OptimisedSolve(const eh_HashState& base_state);
|
||||
bool IsValidSolution(const eh_HashState& base_state, std::vector<eh_index> soln);
|
||||
};
|
||||
|
||||
#include "equihash.tcc"
|
||||
|
||||
static Equihash<96,5> Eh96_5;
|
||||
static Equihash<48,5> Eh48_5;
|
||||
|
||||
#define EhInitialiseState(n, k, base_state) \
|
||||
if (n == 96 && k == 5) { \
|
||||
Eh96_5.InitialiseState(base_state); \
|
||||
} else if (n == 48 && k == 5) { \
|
||||
Eh48_5.InitialiseState(base_state); \
|
||||
} else { \
|
||||
throw std::invalid_argument("Unsupported Equihash parameters"); \
|
||||
}
|
||||
|
||||
#define EhBasicSolve(n, k, base_state, solns) \
|
||||
if (n == 96 && k == 5) { \
|
||||
solns = Eh96_5.BasicSolve(base_state); \
|
||||
} else if (n == 48 && k == 5) { \
|
||||
solns = Eh48_5.BasicSolve(base_state); \
|
||||
} else { \
|
||||
throw std::invalid_argument("Unsupported Equihash parameters"); \
|
||||
}
|
||||
|
||||
#define EhOptimisedSolve(n, k, base_state, solns) \
|
||||
if (n == 96 && k == 5) { \
|
||||
solns = Eh96_5.OptimisedSolve(base_state); \
|
||||
} else if (n == 48 && k == 5) { \
|
||||
solns = Eh48_5.OptimisedSolve(base_state); \
|
||||
} else { \
|
||||
throw std::invalid_argument("Unsupported Equihash parameters"); \
|
||||
}
|
||||
|
||||
#define EhIsValidSolution(n, k, base_state, soln, ret) \
|
||||
if (n == 96 && k == 5) { \
|
||||
ret = Eh96_5.IsValidSolution(base_state, soln); \
|
||||
} else if (n == 48 && k == 5) { \
|
||||
ret = Eh48_5.IsValidSolution(base_state, soln); \
|
||||
} else { \
|
||||
throw std::invalid_argument("Unsupported Equihash parameters"); \
|
||||
}
|
||||
|
||||
#endif // BITCOIN_EQUIHASH_H
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright (c) 2016 Jack Grigg
|
||||
// Copyright (c) 2016 The Zcash developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
|
||||
// Checks if the intersection of a.indices and b.indices is empty
|
||||
template<size_t WIDTH>
|
||||
bool DistinctIndices(const FullStepRow<WIDTH>& a, const FullStepRow<WIDTH>& b, size_t len, size_t lenIndices)
|
||||
{
|
||||
std::vector<eh_index> aSrt = a.GetIndices(len, lenIndices);
|
||||
std::vector<eh_index> bSrt = b.GetIndices(len, lenIndices);
|
||||
|
||||
std::sort(aSrt.begin(), aSrt.end());
|
||||
std::sort(bSrt.begin(), bSrt.end());
|
||||
|
||||
unsigned int i = 0;
|
||||
for (unsigned int j = 0; j < bSrt.size(); j++) {
|
||||
while (aSrt[i] < bSrt[j]) {
|
||||
i++;
|
||||
if (i == aSrt.size()) { return true; }
|
||||
}
|
||||
assert(aSrt[i] >= bSrt[j]);
|
||||
if (aSrt[i] == bSrt[j]) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template<size_t WIDTH>
|
||||
bool IsValidBranch(const FullStepRow<WIDTH>& a, const size_t len, const unsigned int ilen, const eh_trunc t)
|
||||
{
|
||||
return TruncateIndex(ArrayToEhIndex(a.hash+len), ilen) == t;
|
||||
}
|
|
@ -443,7 +443,8 @@ void static BitcoinMiner(CWallet *pwallet)
|
|||
CReserveKey reservekey(pwallet);
|
||||
unsigned int nExtraNonce = 0;
|
||||
|
||||
Equihash eh {chainparams.EquihashN(), chainparams.EquihashK()};
|
||||
unsigned int n = chainparams.EquihashN();
|
||||
unsigned int k = chainparams.EquihashK();
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
|
@ -489,7 +490,7 @@ void static BitcoinMiner(CWallet *pwallet)
|
|||
while (true) {
|
||||
// Hash state
|
||||
crypto_generichash_blake2b_state state;
|
||||
eh.InitialiseState(state);
|
||||
EhInitialiseState(n, k, state);
|
||||
|
||||
// I = the block header minus nonce and solution.
|
||||
CEquihashInput I{*pblock};
|
||||
|
@ -512,7 +513,8 @@ void static BitcoinMiner(CWallet *pwallet)
|
|||
// (x_1, x_2, ...) = A(I, V, n, k)
|
||||
LogPrint("pow", "Running Equihash solver with nNonce = %s\n",
|
||||
pblock->nNonce.ToString());
|
||||
std::set<std::vector<unsigned int>> solns = eh.BasicSolve(curr_state);
|
||||
std::set<std::vector<unsigned int>> solns;
|
||||
EhOptimisedSolve(n, k, curr_state, solns);
|
||||
LogPrint("pow", "Solutions: %d\n", solns.size());
|
||||
|
||||
// Write the solution to the hash and compute the result.
|
||||
|
|
|
@ -88,11 +88,12 @@ unsigned int CalculateNextWorkRequired(const CBlockIndex* pindexLast, int64_t nF
|
|||
|
||||
bool CheckEquihashSolution(const CBlockHeader *pblock, const CChainParams& params)
|
||||
{
|
||||
Equihash eh {params.EquihashN(), params.EquihashK()};
|
||||
unsigned int n = params.EquihashN();
|
||||
unsigned int k = params.EquihashK();
|
||||
|
||||
// Hash state
|
||||
crypto_generichash_blake2b_state state;
|
||||
eh.InitialiseState(state);
|
||||
EhInitialiseState(n, k, state);
|
||||
|
||||
// I = the block header minus nonce and solution.
|
||||
CEquihashInput I{*pblock};
|
||||
|
@ -104,7 +105,9 @@ bool CheckEquihashSolution(const CBlockHeader *pblock, const CChainParams& param
|
|||
// H(I||V||...
|
||||
crypto_generichash_blake2b_update(&state, (unsigned char*)&ss[0], ss.size());
|
||||
|
||||
if (!eh.IsValidSolution(state, pblock->nSolution))
|
||||
bool isValid;
|
||||
EhIsValidSolution(n, k, state, pblock->nSolution, isValid);
|
||||
if (!isValid)
|
||||
return error("CheckEquihashSolution(): invalid solution");
|
||||
|
||||
return true;
|
||||
|
|
|
@ -150,7 +150,8 @@ Value generate(const Array& params, bool fHelp)
|
|||
}
|
||||
unsigned int nExtraNonce = 0;
|
||||
Array blockHashes;
|
||||
Equihash eh {Params().EquihashN(), Params().EquihashK()};
|
||||
unsigned int n = Params().EquihashN();
|
||||
unsigned int k = Params().EquihashK();
|
||||
while (nHeight < nHeightEnd)
|
||||
{
|
||||
auto_ptr<CBlockTemplate> pblocktemplate(CreateNewBlockWithKey(reservekey));
|
||||
|
@ -164,7 +165,7 @@ Value generate(const Array& params, bool fHelp)
|
|||
|
||||
// Hash state
|
||||
crypto_generichash_blake2b_state eh_state;
|
||||
eh.InitialiseState(eh_state);
|
||||
EhInitialiseState(n, k, eh_state);
|
||||
|
||||
// I = the block header minus nonce and solution.
|
||||
CEquihashInput I{*pblock};
|
||||
|
@ -187,10 +188,13 @@ Value generate(const Array& params, bool fHelp)
|
|||
pblock->nNonce.size());
|
||||
|
||||
// (x_1, x_2, ...) = A(I, V, n, k)
|
||||
std::set<std::vector<unsigned int>> solns = eh.BasicSolve(curr_state);
|
||||
std::set<std::vector<unsigned int>> solns;
|
||||
EhBasicSolve(n, k, curr_state, solns);
|
||||
|
||||
for (auto soln : solns) {
|
||||
assert(eh.IsValidSolution(curr_state, soln));
|
||||
bool isValid;
|
||||
EhIsValidSolution(n, k, curr_state, soln, isValid);
|
||||
assert(isValid);
|
||||
pblock->nSolution = soln;
|
||||
|
||||
if (CheckProofOfWork(pblock->GetHash(), pblock->nBits, Params().GetConsensus())) {
|
||||
|
|
|
@ -19,43 +19,7 @@
|
|||
|
||||
BOOST_FIXTURE_TEST_SUITE(equihash_tests, BasicTestingSetup)
|
||||
|
||||
void TestEquihashBasicSolver(unsigned int n, unsigned int k, const std::string &I, const arith_uint256 &nonce, const std::set<std::vector<uint32_t>> &solns) {
|
||||
Equihash eh {n, k};
|
||||
crypto_generichash_blake2b_state state;
|
||||
eh.InitialiseState(state);
|
||||
uint256 V = ArithToUint256(nonce);
|
||||
BOOST_TEST_MESSAGE("Running solver: n = " << n << ", k = " << k << ", I = " << I << ", V = " << V.GetHex());
|
||||
crypto_generichash_blake2b_update(&state, (unsigned char*)&I[0], I.size());
|
||||
crypto_generichash_blake2b_update(&state, V.begin(), V.size());
|
||||
std::set<std::vector<uint32_t>> ret = eh.BasicSolve(state);
|
||||
BOOST_TEST_MESSAGE("Number of solutions: " << ret.size());
|
||||
std::stringstream strm;
|
||||
strm << "{";
|
||||
const char* soln_separator = "";
|
||||
for (std::vector<uint32_t> soln : ret) {
|
||||
strm << soln_separator << "\n {";
|
||||
soln_separator = ",";
|
||||
const char* separator = "";
|
||||
for (uint32_t index : soln) {
|
||||
strm << separator << index;
|
||||
separator = ", ";
|
||||
}
|
||||
strm << "}";
|
||||
}
|
||||
strm << "\n}";
|
||||
BOOST_TEST_MESSAGE(strm.str());
|
||||
BOOST_CHECK(ret == solns);
|
||||
}
|
||||
|
||||
void TestEquihashValidator(unsigned int n, unsigned int k, const std::string &I, const arith_uint256 &nonce, std::vector<uint32_t> soln, bool expected) {
|
||||
Equihash eh {n, k};
|
||||
crypto_generichash_blake2b_state state;
|
||||
eh.InitialiseState(state);
|
||||
uint256 V = ArithToUint256(nonce);
|
||||
crypto_generichash_blake2b_update(&state, (unsigned char*)&I[0], I.size());
|
||||
crypto_generichash_blake2b_update(&state, V.begin(), V.size());
|
||||
BOOST_TEST_MESSAGE("Running validator: n = " << n << ", k = " << k << ", I = " << I << ", V = " << V.GetHex() << ", expected = " << expected << ", soln =");
|
||||
std::stringstream strm;
|
||||
void PrintSolution(std::stringstream &strm, std::vector<uint32_t> soln) {
|
||||
strm << " {";
|
||||
const char* separator = "";
|
||||
for (uint32_t index : soln) {
|
||||
|
@ -63,37 +27,89 @@ void TestEquihashValidator(unsigned int n, unsigned int k, const std::string &I,
|
|||
separator = ", ";
|
||||
}
|
||||
strm << "}";
|
||||
}
|
||||
|
||||
void PrintSolutions(std::stringstream &strm, std::set<std::vector<uint32_t>> solns) {
|
||||
strm << "{";
|
||||
const char* soln_separator = "";
|
||||
for (std::vector<uint32_t> soln : solns) {
|
||||
strm << soln_separator << "\n";
|
||||
soln_separator = ",";
|
||||
PrintSolution(strm, soln);
|
||||
}
|
||||
strm << "\n}";
|
||||
}
|
||||
|
||||
void TestEquihashSolvers(unsigned int n, unsigned int k, const std::string &I, const arith_uint256 &nonce, const std::set<std::vector<uint32_t>> &solns) {
|
||||
crypto_generichash_blake2b_state state;
|
||||
EhInitialiseState(n, k, state);
|
||||
uint256 V = ArithToUint256(nonce);
|
||||
BOOST_TEST_MESSAGE("Running solver: n = " << n << ", k = " << k << ", I = " << I << ", V = " << V.GetHex());
|
||||
crypto_generichash_blake2b_update(&state, (unsigned char*)&I[0], I.size());
|
||||
crypto_generichash_blake2b_update(&state, V.begin(), V.size());
|
||||
|
||||
// First test the basic solver
|
||||
std::set<std::vector<uint32_t>> ret;
|
||||
EhBasicSolve(n, k, state, ret);
|
||||
BOOST_TEST_MESSAGE("[Basic] Number of solutions: " << ret.size());
|
||||
std::stringstream strm;
|
||||
PrintSolutions(strm, ret);
|
||||
BOOST_TEST_MESSAGE(strm.str());
|
||||
BOOST_CHECK(eh.IsValidSolution(state, soln) == expected);
|
||||
BOOST_CHECK(ret == solns);
|
||||
|
||||
// The optimised solver should have the exact same result
|
||||
std::set<std::vector<uint32_t>> retOpt;
|
||||
EhOptimisedSolve(n, k, state, retOpt);
|
||||
BOOST_TEST_MESSAGE("[Optimised] Number of solutions: " << retOpt.size());
|
||||
strm.str("");
|
||||
PrintSolutions(strm, retOpt);
|
||||
BOOST_TEST_MESSAGE(strm.str());
|
||||
BOOST_CHECK(retOpt == solns);
|
||||
BOOST_CHECK(retOpt == ret);
|
||||
}
|
||||
|
||||
void TestEquihashValidator(unsigned int n, unsigned int k, const std::string &I, const arith_uint256 &nonce, std::vector<uint32_t> soln, bool expected) {
|
||||
crypto_generichash_blake2b_state state;
|
||||
EhInitialiseState(n, k, state);
|
||||
uint256 V = ArithToUint256(nonce);
|
||||
crypto_generichash_blake2b_update(&state, (unsigned char*)&I[0], I.size());
|
||||
crypto_generichash_blake2b_update(&state, V.begin(), V.size());
|
||||
BOOST_TEST_MESSAGE("Running validator: n = " << n << ", k = " << k << ", I = " << I << ", V = " << V.GetHex() << ", expected = " << expected << ", soln =");
|
||||
std::stringstream strm;
|
||||
PrintSolution(strm, soln);
|
||||
BOOST_TEST_MESSAGE(strm.str());
|
||||
bool isValid;
|
||||
EhIsValidSolution(n, k, state, soln, isValid);
|
||||
BOOST_CHECK(isValid == expected);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(solver_testvectors) {
|
||||
TestEquihashBasicSolver(96, 5, "block header", 0, {
|
||||
TestEquihashSolvers(96, 5, "block header", 0, {
|
||||
{182, 100500, 71010, 81262, 11318, 81082, 84339, 106327, 25622, 123074, 50681, 128728, 27919, 122921, 33794, 39634, 3948, 33776, 39058, 39177, 35372, 67678, 81195, 120032, 5452, 128944, 110158, 118138, 37893, 65666, 49222, 126229}
|
||||
});
|
||||
TestEquihashBasicSolver(96, 5, "block header", 1, {
|
||||
TestEquihashSolvers(96, 5, "block header", 1, {
|
||||
{1510, 43307, 63800, 74710, 37892, 71424, 63310, 110898, 2260, 70172, 12353, 35063, 13433, 71777, 35871, 80964, 14030, 50499, 35055, 77037, 41990, 79370, 72784, 99843, 16721, 125719, 127888, 131048, 85492, 126861, 89702, 129167},
|
||||
{1623, 18648, 8014, 121335, 5288, 33890, 35968, 74704, 2909, 53346, 41954, 48211, 68872, 110549, 110905, 113986, 20660, 119394, 30054, 37492, 23025, 110409, 55861, 65351, 45769, 128708, 82357, 124990, 76854, 130060, 99713, 119536}
|
||||
});
|
||||
TestEquihashBasicSolver(96, 5, "block header", 2, {
|
||||
TestEquihashSolvers(96, 5, "block header", 2, {
|
||||
{17611, 81207, 44397, 50188, 43411, 119224, 90094, 99790, 21704, 122576, 34295, 98391, 22200, 82614, 108526, 114425, 20019, 69354, 28160, 34999, 31902, 103318, 49332, 65015, 60702, 107535, 76891, 81801, 69559, 83079, 125721, 129893}
|
||||
});
|
||||
TestEquihashBasicSolver(96, 5, "block header", 11, {
|
||||
TestEquihashSolvers(96, 5, "block header", 11, {
|
||||
});
|
||||
|
||||
TestEquihashBasicSolver(96, 5, "Equihash is an asymmetric PoW based on the Generalised Birthday problem.", 0, {
|
||||
TestEquihashSolvers(96, 5, "Equihash is an asymmetric PoW based on the Generalised Birthday problem.", 0, {
|
||||
{2140, 64888, 7062, 37067, 11292, 27641, 53514, 70723, 6685, 73669, 18151, 88834, 55608, 76507, 84243, 125869, 5425, 22827, 37743, 119459, 37587, 118338, 39127, 40622, 16812, 26417, 112391, 120791, 22472, 74552, 43030, 129191},
|
||||
{2742, 14130, 3738, 38739, 60817, 92878, 102087, 102882, 7493, 114098, 11019, 96605, 53351, 65844, 92194, 111605, 12488, 21213, 93833, 103682, 74551, 80813, 93325, 109313, 24782, 124251, 39372, 50621, 35398, 90386, 66867, 79277}
|
||||
});
|
||||
TestEquihashBasicSolver(96, 5, "Equihash is an asymmetric PoW based on the Generalised Birthday problem.", 1, {
|
||||
TestEquihashSolvers(96, 5, "Equihash is an asymmetric PoW based on the Generalised Birthday problem.", 1, {
|
||||
});
|
||||
TestEquihashBasicSolver(96, 5, "Equihash is an asymmetric PoW based on the Generalised Birthday problem.", 2, {
|
||||
TestEquihashSolvers(96, 5, "Equihash is an asymmetric PoW based on the Generalised Birthday problem.", 2, {
|
||||
{2219, 49740, 102167, 108576, 15546, 73320, 29506, 94663, 13900, 74954, 16748, 35617, 42643, 58400, 60768, 63883, 4677, 111178, 35802, 120953, 21542, 89457, 97759, 128494, 24444, 99755, 97152, 108239, 39816, 92800, 85532, 88575},
|
||||
{2258, 41741, 8329, 74706, 8166, 80151, 31480, 86606, 5417, 79683, 97197, 100351, 18608, 61819, 65689, 79940, 13038, 28092, 21997, 62813, 22268, 119557, 58111, 63811, 45789, 72308, 50865, 81180, 91695, 127084, 93402, 95676},
|
||||
{3279, 96607, 78609, 102949, 32765, 54059, 79472, 96147, 25943, 36652, 47276, 71714, 26590, 29892, 44598, 58988, 12323, 42327, 60194, 87786, 60951, 103949, 71481, 81826, 13535, 88167, 17392, 74652, 21924, 64941, 54660, 72151},
|
||||
{8970, 81710, 78816, 97295, 22433, 83703, 59463, 101258, 9014, 75982, 102935, 111574, 27277, 30040, 54221, 107719, 18593, 89276, 94385, 119768, 34013, 63600, 46240, 87288, 46573, 80865, 47845, 67566, 92645, 121901, 102751, 104818}
|
||||
});
|
||||
TestEquihashBasicSolver(96, 5, "Equihash is an asymmetric PoW based on the Generalised Birthday problem.", 11, {
|
||||
TestEquihashSolvers(96, 5, "Equihash is an asymmetric PoW based on the Generalised Birthday problem.", 11, {
|
||||
{3298, 28759, 56287, 109050, 13166, 122018, 75757, 109249, 7616, 83872, 103256, 119576, 43182, 121748, 81417, 120122, 23405, 129542, 68426, 117326, 56427, 118027, 73904, 77697, 41334, 118772, 89089, 130655, 107174, 128610, 107577, 118332}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -98,9 +98,10 @@ double benchmark_solve_equihash()
|
|||
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ss << I;
|
||||
|
||||
Equihash eh {Params(CBaseChainParams::MAIN).EquihashN(), Params(CBaseChainParams::MAIN).EquihashK()};
|
||||
unsigned int n = Params(CBaseChainParams::MAIN).EquihashN();
|
||||
unsigned int k = Params(CBaseChainParams::MAIN).EquihashK();
|
||||
crypto_generichash_blake2b_state eh_state;
|
||||
eh.InitialiseState(eh_state);
|
||||
EhInitialiseState(n, k, eh_state);
|
||||
crypto_generichash_blake2b_update(&eh_state, (unsigned char*)&ss[0], ss.size());
|
||||
|
||||
uint256 nonce;
|
||||
|
@ -110,7 +111,8 @@ double benchmark_solve_equihash()
|
|||
nonce.size());
|
||||
|
||||
timer_start();
|
||||
eh.BasicSolve(eh_state);
|
||||
std::set<std::vector<unsigned int>> solns;
|
||||
EhOptimisedSolve(n, k, eh_state, solns);
|
||||
return timer_stop();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue