Browse Source

Auto merge of #921 - str4d:optimised-equihash, r=daira

Optimise memory usage of Equihash implementation

Part of #857.
pull/4/head
zkbot 8 years ago
parent
commit
55bf149df9
  1. 502
      src/crypto/equihash.cpp
  2. 160
      src/crypto/equihash.h
  3. 35
      src/crypto/equihash.tcc
  4. 8
      src/miner.cpp
  5. 9
      src/pow.cpp
  6. 12
      src/rpcmining.cpp
  7. 88
      src/test/equihash_tests.cpp
  8. 8
      src/zcbenchmarks.cpp

502
src/crypto/equihash.cpp

@ -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);
template<size_t WIDTH> template<size_t W>
StepRow<WIDTH>::StepRow(const StepRow<W>& a)
{
assert(W <= WIDTH);
std::copy(a.hash, a.hash+W, hash);
}
StepRow::~StepRow()
template<size_t WIDTH>
FullStepRow<WIDTH>::FullStepRow(unsigned int n, const eh_HashState& base_state, eh_index i) :
StepRow<WIDTH> {n, base_state, i}
{
delete[] hash;
EhIndexToArray(i, hash+(n/8));
}
StepRow::StepRow(const StepRow& a) :
hash {new unsigned char[a.len]},
len {a.len},
indices(a.indices)
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}
{
for (int i = 0; i < len; i++)
hash[i] = a.hash[i];
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);
}
}
StepRow& StepRow::operator=(const StepRow& a)
template<size_t WIDTH>
FullStepRow<WIDTH>& FullStepRow<WIDTH>::operator=(const FullStepRow<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;
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");
// This doesn't need to be constant time.
for (int i = 0; i < len; i++) {
if (hash[i] != 0)
return false;
}
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;
return true;
}
void StepRow::TrimHash(int l)
template<size_t WIDTH>
std::vector<eh_index> FullStepRow<WIDTH>::GetIndices(size_t len, size_t lenIndices) const
{
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;
std::vector<eh_index> ret;
for (int i = 0; i < lenIndices; i += sizeof(eh_index)) {
ret.push_back(ArrayToEhIndex(hash+len+i));
}
return ret;
}
bool StepRow::IsZero()
template<size_t WIDTH>
bool HasCollision(StepRow<WIDTH>& a, StepRow<WIDTH>& b, int l)
{
char res = 0;
for (int i = 0; i < len; i++)
res |= hash[i];
return res == 0;
// 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;
}
bool HasCollision(StepRow& a, StepRow& b, int l)
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}
{
bool res = true;
for (int j = 0; j < l; j++)
res &= a.hash[j] == b.hash[j];
return res;
hash[n/8] = TruncateIndex(i, ilen);
}
// Checks if the intersection of a.indices and b.indices is empty
bool DistinctIndices(const StepRow& a, const StepRow& b)
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}
{
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; }
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);
}
return true;
}
Equihash::Equihash(unsigned int n, unsigned int k) :
n(n), k(k)
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
{
validate_params(n, k);
eh_trunc* p = new eh_trunc[lenIndices];
std::copy(hash+len, hash+len+lenIndices, p);
return p;
}
std::set<std::vector<eh_index>> Equihash::BasicSolve(const eh_HashState& base_state)
template<unsigned int N, unsigned int K>
std::set<std::vector<eh_index>> Equihash<N,K>::BasicSolve(const eh_HashState& base_state)
{
assert(CollisionBitLength() + 1 < 8*sizeof(eh_index));
eh_index init_size { ((eh_index) 1) << (CollisionBitLength() + 1) };
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)
{
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 };
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);

160
src/crypto/equihash.h

@ -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; }
template<size_t W>
friend bool HasCollision(StepRow<W>& a, StepRow<W>& b, int l);
};
class CompareSR
{
private:
size_t len;
public:
CompareSR(size_t l) : len {l} { }
friend bool HasCollision(StepRow& a, StepRow& b, int l);
friend bool DistinctIndices(const StepRow& a, const StepRow& b);
template<size_t W>
inline bool operator()(const StepRow<W>& a, const StepRow<W>& b) { return memcmp(a.hash, b.hash, len) < 0; }
};
bool HasCollision(StepRow& a, StepRow& b, int l);
bool DistinctIndices(const StepRow& a, const StepRow& b);
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

35
src/crypto/equihash.tcc

@ -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;
}

8
src/miner.cpp

@ -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.

9
src/pow.cpp

@ -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;

12
src/rpcmining.cpp

@ -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())) {

88
src/test/equihash_tests.cpp

@ -19,81 +19,97 @@
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};
void PrintSolution(std::stringstream &strm, std::vector<uint32_t> soln) {
strm << " {";
const char* separator = "";
for (uint32_t index : soln) {
strm << separator << index;
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;
eh.InitialiseState(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());
std::set<std::vector<uint32_t>> ret = eh.BasicSolve(state);
BOOST_TEST_MESSAGE("Number of solutions: " << ret.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;
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}";
PrintSolutions(strm, ret);
BOOST_TEST_MESSAGE(strm.str());
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) {
Equihash eh {n, k};
crypto_generichash_blake2b_state state;
eh.InitialiseState(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;
strm << " {";
const char* separator = "";
for (uint32_t index : soln) {
strm << separator << index;
separator = ", ";
}
strm << "}";
PrintSolution(strm, soln);
BOOST_TEST_MESSAGE(strm.str());
BOOST_CHECK(eh.IsValidSolution(state, soln) == expected);
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}
});
}

8
src/zcbenchmarks.cpp

@ -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…
Cancel
Save