From a3361e778b36aeddb2b0a291da843f5b16007b1c Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sat, 16 Apr 2016 01:14:37 +1200 Subject: [PATCH 01/11] Refactor StepRow to make optimisation easier --- src/crypto/equihash.cpp | 33 ++++++++++--------- src/crypto/equihash.h | 41 +++++++++++++++-------- src/test/equihash_tests.cpp | 65 ++++++++++++++++++++----------------- 3 files changed, 81 insertions(+), 58 deletions(-) diff --git a/src/crypto/equihash.cpp b/src/crypto/equihash.cpp index 1383c73eb..f8bc5ddde 100644 --- a/src/crypto/equihash.cpp +++ b/src/crypto/equihash.cpp @@ -50,15 +50,12 @@ int Equihash::InitialiseState(eh_HashState& base_state) StepRow::StepRow(unsigned int n, const eh_HashState& base_state, eh_index i) : hash {new unsigned char[n/8]}, - len {n/8}, - indices {i} + len {n/8} { 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() @@ -68,14 +65,20 @@ StepRow::~StepRow() StepRow::StepRow(const StepRow& a) : hash {new unsigned char[a.len]}, - len {a.len}, - indices(a.indices) + len {a.len} { for (int i = 0; i < len; i++) hash[i] = a.hash[i]; } -StepRow& StepRow::operator=(const StepRow& a) +FullStepRow::FullStepRow(unsigned int n, const eh_HashState& base_state, eh_index i) : + StepRow {n, base_state, i}, + indices {i} +{ + assert(indices.size() == 1); +} + +FullStepRow& FullStepRow::operator=(const FullStepRow& a) { unsigned char* p = new unsigned char[a.len]; for (int i = 0; i < a.len; i++) @@ -87,7 +90,7 @@ StepRow& StepRow::operator=(const StepRow& a) return *this; } -StepRow& StepRow::operator^=(const StepRow& a) +FullStepRow& FullStepRow::operator^=(const FullStepRow& a) { if (a.len != len) { throw std::invalid_argument("Hash length differs"); @@ -105,7 +108,7 @@ StepRow& StepRow::operator^=(const StepRow& a) return *this; } -void StepRow::TrimHash(int l) +void FullStepRow::TrimHash(int l) { unsigned char* p = new unsigned char[len-l]; for (int i = 0; i < len-l; i++) @@ -132,7 +135,7 @@ bool HasCollision(StepRow& a, StepRow& b, int l) } // Checks if the intersection of a.indices and b.indices is empty -bool DistinctIndices(const StepRow& a, const StepRow& b) +bool DistinctIndices(const FullStepRow& a, const FullStepRow& b) { std::vector aSrt(a.indices); std::vector bSrt(b.indices); @@ -165,7 +168,7 @@ std::set> Equihash::BasicSolve(const eh_HashState& base_st // 1) Generate first list LogPrint("pow", "Generating first list\n"); - std::vector X; + std::vector X; X.reserve(init_size); for (eh_index i = 0; i < init_size; i++) { X.emplace_back(n, base_state, i); @@ -181,7 +184,7 @@ std::set> Equihash::BasicSolve(const eh_HashState& base_st LogPrint("pow", "- Finding collisions\n"); int i = 0; int posFree = 0; - std::vector Xc; + std::vector 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; @@ -233,7 +236,7 @@ std::set> Equihash::BasicSolve(const eh_HashState& base_st std::sort(X.begin(), X.end()); LogPrint("pow", "- Finding collisions\n"); for (int i = 0; i < X.size() - 1; i++) { - StepRow res = X[i] ^ X[i+1]; + FullStepRow res = X[i] ^ X[i+1]; if (res.IsZero() && DistinctIndices(X[i], X[i+1])) { solns.insert(res.GetSolution()); } @@ -252,14 +255,14 @@ bool Equihash::IsValidSolution(const eh_HashState& base_state, std::vector X; + std::vector X; X.reserve(soln_size); for (eh_index i : soln) { X.emplace_back(n, base_state, i); } while (X.size() > 1) { - std::vector Xc; + std::vector Xc; for (int i = 0; i < X.size(); i += 2) { if (!HasCollision(X[i], X[i+1], CollisionByteLength())) { LogPrint("pow", "Invalid solution: invalid collision length between StepRows\n"); diff --git a/src/crypto/equihash.h b/src/crypto/equihash.h index 7aca34208..2603024ae 100644 --- a/src/crypto/equihash.h +++ b/src/crypto/equihash.h @@ -22,38 +22,53 @@ struct invalid_params { }; class StepRow { -private: +protected: unsigned char* hash; unsigned int len; - std::vector indices; public: StepRow(unsigned int n, const eh_HashState& base_state, eh_index i); ~StepRow(); StepRow(const StepRow& a); - StepRow& operator=(const StepRow& a); - StepRow& operator^=(const StepRow& a); - void TrimHash(int l); bool IsZero(); - bool IndicesBefore(const StepRow& a) { return indices[0] < a.indices[0]; } - std::vector GetSolution() { return std::vector(indices); } std::string GetHex() { 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); }; bool HasCollision(StepRow& a, StepRow& b, int l); -bool DistinctIndices(const StepRow& a, const StepRow& b); + +class FullStepRow : public StepRow +{ +private: + std::vector indices; + +public: + FullStepRow(unsigned int n, const eh_HashState& base_state, eh_index i); + ~FullStepRow() { } + + FullStepRow(const FullStepRow& a) : StepRow {a}, indices(a.indices) { } + FullStepRow& operator=(const FullStepRow& a); + FullStepRow& operator^=(const FullStepRow& a); + + void TrimHash(int l); + bool IndicesBefore(const FullStepRow& a) { return indices[0] < a.indices[0]; } + std::vector GetSolution() { return std::vector(indices); } + + friend inline const FullStepRow operator^(const FullStepRow& a, const FullStepRow& b) { + if (a.indices[0] < b.indices[0]) { return FullStepRow(a) ^= b; } + else { return FullStepRow(b) ^= a; } + } + + friend bool DistinctIndices(const FullStepRow& a, const FullStepRow& b); +}; + +bool DistinctIndices(const FullStepRow& a, const FullStepRow& b); class Equihash { diff --git a/src/test/equihash_tests.cpp b/src/test/equihash_tests.cpp index 3d57258db..2066af919 100644 --- a/src/test/equihash_tests.cpp +++ b/src/test/equihash_tests.cpp @@ -19,7 +19,28 @@ 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> &solns) { +void PrintSolution(std::stringstream &strm, std::vector soln) { + strm << " {"; + const char* separator = ""; + for (uint32_t index : soln) { + strm << separator << index; + separator = ", "; + } + strm << "}"; +} + +void PrintSolutions(std::stringstream &strm, std::set> solns) { + strm << "{"; + const char* soln_separator = ""; + for (std::vector 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> &solns) { Equihash eh {n, k}; crypto_generichash_blake2b_state state; eh.InitialiseState(state); @@ -27,22 +48,12 @@ void TestEquihashBasicSolver(unsigned int n, unsigned int k, const std::string & 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> ret = eh.BasicSolve(state); - BOOST_TEST_MESSAGE("Number of solutions: " << ret.size()); + BOOST_TEST_MESSAGE("[Basic] Number of solutions: " << ret.size()); std::stringstream strm; - strm << "{"; - const char* soln_separator = ""; - for (std::vector 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); } @@ -56,44 +67,38 @@ void TestEquihashValidator(unsigned int n, unsigned int k, const std::string &I, 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); } 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} }); } From 6afef0dd6d76089eebee2507af1b75fad20d9c1b Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 20 Apr 2016 22:44:41 +1200 Subject: [PATCH 02/11] Cleanups --- src/crypto/equihash.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/crypto/equihash.cpp b/src/crypto/equihash.cpp index f8bc5ddde..41718918a 100644 --- a/src/crypto/equihash.cpp +++ b/src/crypto/equihash.cpp @@ -67,8 +67,7 @@ StepRow::StepRow(const StepRow& a) : hash {new unsigned char[a.len]}, len {a.len} { - for (int i = 0; i < len; i++) - hash[i] = a.hash[i]; + std::copy(a.hash, a.hash+a.len, hash); } FullStepRow::FullStepRow(unsigned int n, const eh_HashState& base_state, eh_index i) : @@ -81,8 +80,7 @@ FullStepRow::FullStepRow(unsigned int n, const eh_HashState& base_state, eh_inde FullStepRow& FullStepRow::operator=(const FullStepRow& a) { unsigned char* p = new unsigned char[a.len]; - for (int i = 0; i < a.len; i++) - p[i] = a.hash[i]; + std::copy(a.hash, a.hash+a.len, p); delete[] hash; hash = p; len = a.len; @@ -111,8 +109,7 @@ FullStepRow& FullStepRow::operator^=(const FullStepRow& a) void FullStepRow::TrimHash(int l) { unsigned char* p = new unsigned char[len-l]; - for (int i = 0; i < len-l; i++) - p[i] = hash[i+l]; + std::copy(hash+l, hash+len, p); delete[] hash; hash = p; len -= l; @@ -164,7 +161,7 @@ Equihash::Equihash(unsigned int n, unsigned int k) : std::set> Equihash::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"); From c92c1f60508b8ebac0a2cad37cc243501605fee2 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Tue, 19 Apr 2016 13:41:06 +1200 Subject: [PATCH 03/11] Implement index-truncation Equihash optimisation --- src/crypto/equihash.cpp | 255 ++++++++++++++++++++++++++++++++++++ src/crypto/equihash.h | 26 ++++ src/test/equihash_tests.cpp | 9 ++ 3 files changed, 290 insertions(+) diff --git a/src/crypto/equihash.cpp b/src/crypto/equihash.cpp index 41718918a..ae6585fb0 100644 --- a/src/crypto/equihash.cpp +++ b/src/crypto/equihash.cpp @@ -48,6 +48,19 @@ int Equihash::InitialiseState(eh_HashState& base_state) personalization); } +eh_trunc TruncateIndex(eh_index i, unsigned int ilen) +{ + // Truncate to 8 bits + assert(sizeof(eh_trunc) == 1); + return (i >> (ilen - 8)) & 0xff; +} + +eh_index UntruncateIndex(eh_trunc t, eh_index r, unsigned int ilen) +{ + eh_index i{t}; + return (i << (ilen - 8)) | r; +} + StepRow::StepRow(unsigned int n, const eh_HashState& base_state, eh_index i) : hash {new unsigned char[n/8]}, len {n/8} @@ -152,6 +165,47 @@ bool DistinctIndices(const FullStepRow& a, const FullStepRow& b) return true; } +bool IsValidBranch(const FullStepRow& a, const unsigned int ilen, const eh_trunc t) +{ + return TruncateIndex(a.indices[0], ilen) == t; +} + +TruncatedStepRow::TruncatedStepRow(unsigned int n, const eh_HashState& base_state, eh_index i, unsigned int ilen) : + StepRow {n, base_state, i}, + indices {TruncateIndex(i, ilen)} +{ + assert(indices.size() == 1); +} + +TruncatedStepRow& TruncatedStepRow::operator=(const TruncatedStepRow& a) +{ + unsigned char* p = new unsigned char[a.len]; + std::copy(a.hash, a.hash+a.len, p); + delete[] hash; + hash = p; + len = a.len; + indices = a.indices; + return *this; +} + +TruncatedStepRow& TruncatedStepRow::operator^=(const TruncatedStepRow& a) +{ + 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; +} + Equihash::Equihash(unsigned int n, unsigned int k) : n(n), k(k) { @@ -244,6 +298,207 @@ std::set> Equihash::BasicSolve(const eh_HashState& base_st return solns; } +void CollideBranches(std::vector& X, const unsigned int clen, const unsigned int ilen, const eh_trunc lt, const eh_trunc rt) +{ + int i = 0; + int posFree = 0; + std::vector 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])) { + if (IsValidBranch(X[i+l], ilen, lt) && IsValidBranch(X[i+m], ilen, rt)) { + Xc.push_back(X[i+l] ^ X[i+m]); + Xc.back().TrimHash(clen); + } else if (IsValidBranch(X[i+m], ilen, lt) && IsValidBranch(X[i+l], ilen, rt)) { + Xc.push_back(X[i+m] ^ X[i+l]); + Xc.back().TrimHash(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(); + } +} + +std::set> Equihash::OptimisedSolve(const eh_HashState& base_state) +{ + assert(CollisionBitLength() + 1 < 8*sizeof(eh_index)); + eh_index init_size { 1 << (CollisionBitLength() + 1) }; + + // First run the algorithm with truncated indices + + std::vector> partialSolns; + { + + // 1) Generate first list + LogPrint("pow", "Generating first list\n"); + std::vector 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()); + + LogPrint("pow", "- Finding collisions\n"); + int i = 0; + int posFree = 0; + std::vector 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.push_back(Xt[i+l] ^ Xt[i+m]); + Xc.back().TrimHash(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(); + } + } + + // 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()); + LogPrint("pow", "- Finding collisions\n"); + for (int i = 0; i < Xt.size() - 1; i++) { + TruncatedStepRow res = Xt[i] ^ Xt[i+1]; + if (res.IsZero()) { + partialSolns.push_back(res.GetPartialSolution()); + } + } + } 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> solns; + eh_index recreate_size { UntruncateIndex(1, 0, CollisionBitLength() + 1) }; + int invalidCount = 0; + for (std::vector partialSoln : partialSolns) { + // 1) Generate first list of possibilities + std::vector> X; + X.reserve(partialSoln.size()); + for (int i = 0; i < partialSoln.size(); i++) { + std::vector 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> 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 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()); + CollideBranches(ic, CollisionByteLength(), CollisionBitLength() + 1, partialSoln[(1< soln) { eh_index soln_size { 1u << k }; diff --git a/src/crypto/equihash.h b/src/crypto/equihash.h index 2603024ae..32ae011c5 100644 --- a/src/crypto/equihash.h +++ b/src/crypto/equihash.h @@ -17,6 +17,7 @@ typedef crypto_generichash_blake2b_state eh_HashState; typedef uint32_t eh_index; +typedef uint8_t eh_trunc; struct invalid_params { }; @@ -66,9 +67,33 @@ public: } friend bool DistinctIndices(const FullStepRow& a, const FullStepRow& b); + friend bool IsValidBranch(const FullStepRow& a, const unsigned int ilen, const eh_trunc t); }; bool DistinctIndices(const FullStepRow& a, const FullStepRow& b); +bool IsValidBranch(const FullStepRow& a, const unsigned int ilen, const eh_trunc t); + +class TruncatedStepRow : public StepRow +{ +private: + std::vector indices; + +public: + TruncatedStepRow(unsigned int n, const eh_HashState& base_state, eh_index i, unsigned int ilen); + ~TruncatedStepRow() { } + + TruncatedStepRow(const TruncatedStepRow& a) : StepRow {a}, indices(a.indices) { } + TruncatedStepRow& operator=(const TruncatedStepRow& a); + TruncatedStepRow& operator^=(const TruncatedStepRow& a); + + bool IndicesBefore(const TruncatedStepRow& a) { return indices[0] < a.indices[0]; } + std::vector GetPartialSolution() { return std::vector(indices); } + + friend inline const TruncatedStepRow operator^(const TruncatedStepRow& a, const TruncatedStepRow& b) { + if (a.indices[0] < b.indices[0]) { return TruncatedStepRow(a) ^= b; } + else { return TruncatedStepRow(b) ^= a; } + } +}; class Equihash { @@ -84,6 +109,7 @@ public: int InitialiseState(eh_HashState& base_state); std::set> BasicSolve(const eh_HashState& base_state); + std::set> OptimisedSolve(const eh_HashState& base_state); bool IsValidSolution(const eh_HashState& base_state, std::vector soln); }; diff --git a/src/test/equihash_tests.cpp b/src/test/equihash_tests.cpp index 2066af919..0d28c2771 100644 --- a/src/test/equihash_tests.cpp +++ b/src/test/equihash_tests.cpp @@ -56,6 +56,15 @@ void TestEquihashSolvers(unsigned int n, unsigned int k, const std::string &I, c PrintSolutions(strm, ret); BOOST_TEST_MESSAGE(strm.str()); BOOST_CHECK(ret == solns); + + // The optimised solver should have the exact same result + std::set> retOpt = eh.OptimisedSolve(state); + 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 soln, bool expected) { From 39f5cb35f96d9c16061880b1c8d4cfa18c9f5e28 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 4 May 2016 13:47:49 +1200 Subject: [PATCH 04/11] Store truncated indices in the same char* as the hash (H/T tromp for the idea!) --- src/crypto/equihash.cpp | 66 +++++++++++++++++++++++++++++++---------- src/crypto/equihash.h | 11 +++---- 2 files changed, 57 insertions(+), 20 deletions(-) diff --git a/src/crypto/equihash.cpp b/src/crypto/equihash.cpp index ae6585fb0..1511d383a 100644 --- a/src/crypto/equihash.cpp +++ b/src/crypto/equihash.cpp @@ -172,19 +172,33 @@ bool IsValidBranch(const FullStepRow& a, const unsigned int ilen, const eh_trunc TruncatedStepRow::TruncatedStepRow(unsigned int n, const eh_HashState& base_state, eh_index i, unsigned int ilen) : StepRow {n, base_state, i}, - indices {TruncateIndex(i, ilen)} + lenIndices {1} { - assert(indices.size() == 1); + unsigned char* p = new unsigned char[len+lenIndices]; + std::copy(hash, hash+len, p); + p[len] = TruncateIndex(i, ilen); + delete[] hash; + hash = p; +} + +TruncatedStepRow::TruncatedStepRow(const TruncatedStepRow& a) : + StepRow {a}, + lenIndices {a.lenIndices} +{ + unsigned char* p = new unsigned char[a.len+a.lenIndices]; + std::copy(a.hash, a.hash+a.len+a.lenIndices, p); + delete[] hash; + hash = p; } TruncatedStepRow& TruncatedStepRow::operator=(const TruncatedStepRow& a) { - unsigned char* p = new unsigned char[a.len]; - std::copy(a.hash, a.hash+a.len, p); + unsigned char* p = new unsigned char[a.len+a.lenIndices]; + std::copy(a.hash, a.hash+a.len+a.lenIndices, p); delete[] hash; hash = p; len = a.len; - indices = a.indices; + lenIndices = a.lenIndices; return *this; } @@ -193,19 +207,37 @@ TruncatedStepRow& TruncatedStepRow::operator^=(const TruncatedStepRow& a) if (a.len != len) { throw std::invalid_argument("Hash length differs"); } - if (a.indices.size() != indices.size()) { + if (a.lenIndices != lenIndices) { throw std::invalid_argument("Number of indices differs"); } - unsigned char* p = new unsigned char[len]; + unsigned char* p = new unsigned char[len+lenIndices+a.lenIndices]; for (int i = 0; i < len; i++) p[i] = hash[i] ^ a.hash[i]; + std::copy(hash+len, hash+len+lenIndices, p+len); + std::copy(a.hash+a.len, a.hash+a.len+a.lenIndices, p+len+lenIndices); delete[] hash; hash = p; - indices.reserve(indices.size() + a.indices.size()); - indices.insert(indices.end(), a.indices.begin(), a.indices.end()); + lenIndices += a.lenIndices; return *this; } +void TruncatedStepRow::TrimHash(int l) +{ + unsigned char* p = new unsigned char[len-l+lenIndices]; + std::copy(hash+l, hash+len+lenIndices, p); + delete[] hash; + hash = p; + len -= l; +} + +eh_trunc* TruncatedStepRow::GetPartialSolution(eh_index soln_size) const +{ + assert(lenIndices == soln_size); + eh_trunc* p = new eh_trunc[lenIndices]; + std::copy(hash+len, hash+len+lenIndices, p); + return p; +} + Equihash::Equihash(unsigned int n, unsigned int k) : n(n), k(k) { @@ -358,7 +390,8 @@ std::set> Equihash::OptimisedSolve(const eh_HashState& bas // First run the algorithm with truncated indices - std::vector> partialSolns; + eh_index soln_size { 1 << k }; + std::vector partialSolns; { // 1) Generate first list @@ -431,7 +464,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState& bas for (int i = 0; i < Xt.size() - 1; i++) { TruncatedStepRow res = Xt[i] ^ Xt[i+1]; if (res.IsZero()) { - partialSolns.push_back(res.GetPartialSolution()); + partialSolns.push_back(res.GetPartialSolution(soln_size)); } } } else @@ -446,11 +479,11 @@ std::set> Equihash::OptimisedSolve(const eh_HashState& bas std::set> solns; eh_index recreate_size { UntruncateIndex(1, 0, CollisionBitLength() + 1) }; int invalidCount = 0; - for (std::vector partialSoln : partialSolns) { + for (eh_trunc* partialSoln : partialSolns) { // 1) Generate first list of possibilities std::vector> X; - X.reserve(partialSoln.size()); - for (int i = 0; i < partialSoln.size(); i++) { + X.reserve(soln_size); + for (eh_index i = 0; i < soln_size; i++) { std::vector ic; ic.reserve(recreate_size); for (eh_index j = 0; j < recreate_size; j++) { @@ -489,10 +522,13 @@ std::set> Equihash::OptimisedSolve(const eh_HashState& bas for (FullStepRow row : X[0]) { solns.insert(row.GetSolution()); } - continue; + goto deletesolution; invalidsolution: invalidCount++; + +deletesolution: + delete[] partialSoln; } LogPrint("pow", "- Number of invalid solutions found: %d\n", invalidCount); diff --git a/src/crypto/equihash.h b/src/crypto/equihash.h index 32ae011c5..a94c149af 100644 --- a/src/crypto/equihash.h +++ b/src/crypto/equihash.h @@ -76,21 +76,22 @@ bool IsValidBranch(const FullStepRow& a, const unsigned int ilen, const eh_trunc class TruncatedStepRow : public StepRow { private: - std::vector indices; + unsigned int lenIndices; public: TruncatedStepRow(unsigned int n, const eh_HashState& base_state, eh_index i, unsigned int ilen); ~TruncatedStepRow() { } - TruncatedStepRow(const TruncatedStepRow& a) : StepRow {a}, indices(a.indices) { } + TruncatedStepRow(const TruncatedStepRow& a); TruncatedStepRow& operator=(const TruncatedStepRow& a); TruncatedStepRow& operator^=(const TruncatedStepRow& a); - bool IndicesBefore(const TruncatedStepRow& a) { return indices[0] < a.indices[0]; } - std::vector GetPartialSolution() { return std::vector(indices); } + void TrimHash(int l); + inline bool IndicesBefore(const TruncatedStepRow& a) const { return memcmp(hash+len, a.hash+a.len, lenIndices) < 0; } + eh_trunc* GetPartialSolution(eh_index soln_size) const; friend inline const TruncatedStepRow operator^(const TruncatedStepRow& a, const TruncatedStepRow& b) { - if (a.indices[0] < b.indices[0]) { return TruncatedStepRow(a) ^= b; } + if (a.IndicesBefore(b)) { return TruncatedStepRow(a) ^= b; } else { return TruncatedStepRow(b) ^= a; } } }; From e95747288a9b320969d854ef4062c2aa646ca9a4 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 5 May 2016 15:00:44 +1200 Subject: [PATCH 05/11] Use template parameters to statically initialise Equihash --- src/crypto/equihash.cpp | 90 +++++++++++++++++-------------------- src/crypto/equihash.h | 56 ++++++++++++++++++++--- src/miner.cpp | 8 ++-- src/pow.cpp | 9 ++-- src/rpcmining.cpp | 12 +++-- src/test/equihash_tests.cpp | 16 ++++--- src/zcbenchmarks.cpp | 8 ++-- 7 files changed, 124 insertions(+), 75 deletions(-) diff --git a/src/crypto/equihash.cpp b/src/crypto/equihash.cpp index 1511d383a..77a4d5856 100644 --- a/src/crypto/equihash.cpp +++ b/src/crypto/equihash.cpp @@ -19,31 +19,18 @@ #include #include -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 +int Equihash::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); } @@ -238,27 +225,21 @@ eh_trunc* TruncatedStepRow::GetPartialSolution(eh_index soln_size) const return p; } -Equihash::Equihash(unsigned int n, unsigned int k) : - n(n), k(k) +template +std::set> Equihash::BasicSolve(const eh_HashState& base_state) { - validate_params(n, k); -} - -std::set> Equihash::BasicSolve(const eh_HashState& base_state) -{ - assert(CollisionBitLength() + 1 < 8*sizeof(eh_index)); - eh_index init_size { 1 << (CollisionBitLength() + 1) }; + eh_index init_size { 1 << (CollisionBitLength + 1) }; // 1) Generate first list LogPrint("pow", "Generating first list\n"); std::vector 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"); @@ -272,7 +253,7 @@ std::set> Equihash::BasicSolve(const eh_HashState& base_st // 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++; } @@ -281,7 +262,7 @@ std::set> Equihash::BasicSolve(const eh_HashState& base_st 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()); + Xc.back().TrimHash(CollisionByteLength); } } } @@ -383,14 +364,14 @@ void CollideBranches(std::vector& X, const unsigned int clen, const } } -std::set> Equihash::OptimisedSolve(const eh_HashState& base_state) +template +std::set> Equihash::OptimisedSolve(const eh_HashState& base_state) { - assert(CollisionBitLength() + 1 < 8*sizeof(eh_index)); - eh_index init_size { 1 << (CollisionBitLength() + 1) }; + eh_index init_size { 1 << (CollisionBitLength + 1) }; // First run the algorithm with truncated indices - eh_index soln_size { 1 << k }; + eh_index soln_size { 1 << K }; std::vector partialSolns; { @@ -399,11 +380,11 @@ std::set> Equihash::OptimisedSolve(const eh_HashState& bas std::vector Xt; Xt.reserve(init_size); for (eh_index i = 0; i < init_size; i++) { - Xt.emplace_back(n, base_state, i, CollisionBitLength() + 1); + 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++) { + 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"); @@ -417,7 +398,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState& bas // 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())) { + HasCollision(Xt[i], Xt[i+j], CollisionByteLength)) { j++; } @@ -426,7 +407,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState& bas for (int m = l + 1; m < j; m++) { // We truncated, so don't check for distinct indices here Xc.push_back(Xt[i+l] ^ Xt[i+m]); - Xc.back().TrimHash(CollisionByteLength()); + Xc.back().TrimHash(CollisionByteLength); } } @@ -477,7 +458,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState& bas // Now for each solution run the algorithm again to recreate the indices LogPrint("pow", "Culling solutions\n"); std::set> solns; - eh_index recreate_size { UntruncateIndex(1, 0, CollisionBitLength() + 1) }; + eh_index recreate_size { UntruncateIndex(1, 0, CollisionBitLength + 1) }; int invalidCount = 0; for (eh_trunc* partialSoln : partialSolns) { // 1) Generate first list of possibilities @@ -487,8 +468,8 @@ std::set> Equihash::OptimisedSolve(const eh_HashState& bas std::vector 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); + eh_index newIndex { UntruncateIndex(partialSoln[i], j, CollisionBitLength + 1) }; + ic.emplace_back(N, base_state, newIndex); } X.push_back(ic); } @@ -505,7 +486,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState& bas 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()); - CollideBranches(ic, CollisionByteLength(), CollisionBitLength() + 1, partialSoln[(1< soln) +template +bool Equihash::IsValidSolution(const eh_HashState& base_state, std::vector 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; @@ -546,13 +528,13 @@ bool Equihash::IsValidSolution(const eh_HashState& base_state, std::vector X; X.reserve(soln_size); for (eh_index i : soln) { - X.emplace_back(n, base_state, i); + X.emplace_back(N, base_state, i); } while (X.size() > 1) { std::vector 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()); @@ -567,7 +549,7 @@ bool Equihash::IsValidSolution(const eh_HashState& base_state, std::vector +template int Equihash<96,5>::InitialiseState(eh_HashState& base_state); +template std::set> Equihash<96,5>::BasicSolve(const eh_HashState& base_state); +template std::set> Equihash<96,5>::OptimisedSolve(const eh_HashState& base_state); +template bool Equihash<96,5>::IsValidSolution(const eh_HashState& base_state, std::vector soln); + +// Explicit instantiations for Equihash<48,5> +template int Equihash<48,5>::InitialiseState(eh_HashState& base_state); +template std::set> Equihash<48,5>::BasicSolve(const eh_HashState& base_state); +template std::set> Equihash<48,5>::OptimisedSolve(const eh_HashState& base_state); +template bool Equihash<48,5>::IsValidSolution(const eh_HashState& base_state, std::vector soln); diff --git a/src/crypto/equihash.h b/src/crypto/equihash.h index a94c149af..4e067155d 100644 --- a/src/crypto/equihash.h +++ b/src/crypto/equihash.h @@ -15,12 +15,12 @@ #include #include +#include + typedef crypto_generichash_blake2b_state eh_HashState; typedef uint32_t eh_index; typedef uint8_t eh_trunc; -struct invalid_params { }; - class StepRow { protected: @@ -96,17 +96,20 @@ public: } }; +template 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 }; - inline unsigned int CollisionBitLength() { return n/(k+1); } - inline unsigned int CollisionByteLength() { return CollisionBitLength()/8; } + Equihash() { } int InitialiseState(eh_HashState& base_state); std::set> BasicSolve(const eh_HashState& base_state); @@ -114,4 +117,43 @@ public: bool IsValidSolution(const eh_HashState& base_state, std::vector soln); }; +static Equihash<96,5> Eh965; +static Equihash<48,5> Eh485; + +#define EhInitialiseState(n, k, base_state) \ + if (n == 96 && k == 5) { \ + Eh965.InitialiseState(base_state); \ + } else if (n == 48 && k == 5) { \ + Eh485.InitialiseState(base_state); \ + } else { \ + throw std::invalid_argument("Unsupported Equihash parameters"); \ + } + +#define EhBasicSolve(n, k, base_state, solns) \ + if (n == 96 && k == 5) { \ + solns = Eh965.BasicSolve(base_state); \ + } else if (n == 48 && k == 5) { \ + solns = Eh485.BasicSolve(base_state); \ + } else { \ + throw std::invalid_argument("Unsupported Equihash parameters"); \ + } + +#define EhOptimisedSolve(n, k, base_state, solns) \ + if (n == 96 && k == 5) { \ + solns = Eh965.OptimisedSolve(base_state); \ + } else if (n == 48 && k == 5) { \ + solns = Eh485.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 = Eh965.IsValidSolution(base_state, soln); \ + } else if (n == 48 && k == 5) { \ + ret = Eh485.IsValidSolution(base_state, soln); \ + } else { \ + throw std::invalid_argument("Unsupported Equihash parameters"); \ + } + #endif // BITCOIN_EQUIHASH_H diff --git a/src/miner.cpp b/src/miner.cpp index 1d7984aa5..e3d89d3a8 100644 --- a/src/miner.cpp +++ b/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> solns = eh.BasicSolve(curr_state); + std::set> solns; + EhBasicSolve(n, k, curr_state, solns); LogPrint("pow", "Solutions: %d\n", solns.size()); // Write the solution to the hash and compute the result. diff --git a/src/pow.cpp b/src/pow.cpp index ebcba1dae..125d59125 100644 --- a/src/pow.cpp +++ b/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; diff --git a/src/rpcmining.cpp b/src/rpcmining.cpp index 437311d17..c8a809c19 100644 --- a/src/rpcmining.cpp +++ b/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 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> solns = eh.BasicSolve(curr_state); + std::set> 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())) { diff --git a/src/test/equihash_tests.cpp b/src/test/equihash_tests.cpp index 0d28c2771..d7fdf5f63 100644 --- a/src/test/equihash_tests.cpp +++ b/src/test/equihash_tests.cpp @@ -41,16 +41,16 @@ void PrintSolutions(std::stringstream &strm, std::set> sol } void TestEquihashSolvers(unsigned int n, unsigned int k, const std::string &I, const arith_uint256 &nonce, const std::set> &solns) { - Equihash eh {n, k}; 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()); // First test the basic solver - std::set> ret = eh.BasicSolve(state); + std::set> ret; + EhBasicSolve(n, k, state, ret); BOOST_TEST_MESSAGE("[Basic] Number of solutions: " << ret.size()); std::stringstream strm; PrintSolutions(strm, ret); @@ -58,7 +58,8 @@ void TestEquihashSolvers(unsigned int n, unsigned int k, const std::string &I, c BOOST_CHECK(ret == solns); // The optimised solver should have the exact same result - std::set> retOpt = eh.OptimisedSolve(state); + std::set> retOpt; + EhOptimisedSolve(n, k, state, retOpt); BOOST_TEST_MESSAGE("[Optimised] Number of solutions: " << retOpt.size()); strm.str(""); PrintSolutions(strm, retOpt); @@ -68,9 +69,8 @@ void TestEquihashSolvers(unsigned int n, unsigned int k, const std::string &I, c } void TestEquihashValidator(unsigned int n, unsigned int k, const std::string &I, const arith_uint256 &nonce, std::vector 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()); @@ -78,7 +78,9 @@ void TestEquihashValidator(unsigned int n, unsigned int k, const std::string &I, std::stringstream 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) { diff --git a/src/zcbenchmarks.cpp b/src/zcbenchmarks.cpp index 34c9295a1..70028917c 100644 --- a/src/zcbenchmarks.cpp +++ b/src/zcbenchmarks.cpp @@ -100,9 +100,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; @@ -112,7 +113,8 @@ double benchmark_solve_equihash() nonce.size()); timer_start(); - eh.BasicSolve(eh_state); + std::set> solns; + EhBasicSolve(n, k, eh_state, solns); return timer_stop(); } From a683cc85d925f4133f850023540587a2e6e1d9ff Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 5 May 2016 17:05:27 +1200 Subject: [PATCH 06/11] Merge *StepRow XOR and trimming operations --- src/crypto/equihash.cpp | 124 ++++++++++++++++++---------------------- src/crypto/equihash.h | 18 +----- 2 files changed, 60 insertions(+), 82 deletions(-) diff --git a/src/crypto/equihash.cpp b/src/crypto/equihash.cpp index 77a4d5856..e66e8e058 100644 --- a/src/crypto/equihash.cpp +++ b/src/crypto/equihash.cpp @@ -77,6 +77,31 @@ FullStepRow::FullStepRow(unsigned int n, const eh_HashState& base_state, eh_inde assert(indices.size() == 1); } +FullStepRow::FullStepRow(const FullStepRow& a, const FullStepRow& b, int trim) : + StepRow {a} +{ + if (a.len != b.len) { + throw std::invalid_argument("Hash length differs"); + } + if (a.indices.size() != b.indices.size()) { + throw std::invalid_argument("Number of indices differs"); + } + unsigned char* p = new unsigned char[a.len-trim]; + for (int i = trim; i < a.len; i++) + p[i-trim] = a.hash[i] ^ b.hash[i]; + delete[] hash; + hash = p; + len = a.len-trim; + indices.reserve(a.indices.size() + b.indices.size()); + if (a.IndicesBefore(b)) { + indices.insert(indices.end(), a.indices.begin(), a.indices.end()); + indices.insert(indices.end(), b.indices.begin(), b.indices.end()); + } else { + indices.insert(indices.end(), b.indices.begin(), b.indices.end()); + indices.insert(indices.end(), a.indices.begin(), a.indices.end()); + } +} + FullStepRow& FullStepRow::operator=(const FullStepRow& a) { unsigned char* p = new unsigned char[a.len]; @@ -88,33 +113,6 @@ FullStepRow& FullStepRow::operator=(const FullStepRow& a) return *this; } -FullStepRow& FullStepRow::operator^=(const FullStepRow& a) -{ - 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 FullStepRow::TrimHash(int l) -{ - unsigned char* p = new unsigned char[len-l]; - std::copy(hash+l, hash+len, p); - delete[] hash; - hash = p; - len -= l; -} - bool StepRow::IsZero() { char res = 0; @@ -178,6 +176,31 @@ TruncatedStepRow::TruncatedStepRow(const TruncatedStepRow& a) : hash = p; } +TruncatedStepRow::TruncatedStepRow(const TruncatedStepRow& a, const TruncatedStepRow& b, int trim) : + StepRow {a}, + lenIndices {a.lenIndices+b.lenIndices} +{ + if (a.len != b.len) { + throw std::invalid_argument("Hash length differs"); + } + if (a.lenIndices != b.lenIndices) { + throw std::invalid_argument("Number of indices differs"); + } + unsigned char* p = new unsigned char[a.len-trim+a.lenIndices+b.lenIndices]; + for (int i = trim; i < a.len; i++) + p[i-trim] = a.hash[i] ^ b.hash[i]; + len = a.len-trim; + if (a.IndicesBefore(b)) { + std::copy(a.hash+a.len, a.hash+a.len+a.lenIndices, p+len); + std::copy(b.hash+b.len, b.hash+b.len+b.lenIndices, p+len+a.lenIndices); + } else { + std::copy(b.hash+b.len, b.hash+b.len+b.lenIndices, p+len); + std::copy(a.hash+a.len, a.hash+a.len+a.lenIndices, p+len+b.lenIndices); + } + delete[] hash; + hash = p; +} + TruncatedStepRow& TruncatedStepRow::operator=(const TruncatedStepRow& a) { unsigned char* p = new unsigned char[a.len+a.lenIndices]; @@ -189,34 +212,6 @@ TruncatedStepRow& TruncatedStepRow::operator=(const TruncatedStepRow& a) return *this; } -TruncatedStepRow& TruncatedStepRow::operator^=(const TruncatedStepRow& a) -{ - if (a.len != len) { - throw std::invalid_argument("Hash length differs"); - } - if (a.lenIndices != lenIndices) { - throw std::invalid_argument("Number of indices differs"); - } - unsigned char* p = new unsigned char[len+lenIndices+a.lenIndices]; - for (int i = 0; i < len; i++) - p[i] = hash[i] ^ a.hash[i]; - std::copy(hash+len, hash+len+lenIndices, p+len); - std::copy(a.hash+a.len, a.hash+a.len+a.lenIndices, p+len+lenIndices); - delete[] hash; - hash = p; - lenIndices += a.lenIndices; - return *this; -} - -void TruncatedStepRow::TrimHash(int l) -{ - unsigned char* p = new unsigned char[len-l+lenIndices]; - std::copy(hash+l, hash+len+lenIndices, p); - delete[] hash; - hash = p; - len -= l; -} - eh_trunc* TruncatedStepRow::GetPartialSolution(eh_index soln_size) const { assert(lenIndices == soln_size); @@ -261,8 +256,7 @@ std::set> Equihash::BasicSolve(const eh_HashState& ba 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); + Xc.emplace_back(X[i+l], X[i+m], CollisionByteLength); } } } @@ -300,7 +294,7 @@ std::set> Equihash::BasicSolve(const eh_HashState& ba std::sort(X.begin(), X.end()); LogPrint("pow", "- Finding collisions\n"); for (int i = 0; i < X.size() - 1; i++) { - FullStepRow res = X[i] ^ X[i+1]; + FullStepRow res(X[i], X[i+1], 0); if (res.IsZero() && DistinctIndices(X[i], X[i+1])) { solns.insert(res.GetSolution()); } @@ -329,11 +323,9 @@ void CollideBranches(std::vector& X, const unsigned int clen, const for (int m = l + 1; m < j; m++) { if (DistinctIndices(X[i+l], X[i+m])) { if (IsValidBranch(X[i+l], ilen, lt) && IsValidBranch(X[i+m], ilen, rt)) { - Xc.push_back(X[i+l] ^ X[i+m]); - Xc.back().TrimHash(clen); + Xc.emplace_back(X[i+l], X[i+m], clen); } else if (IsValidBranch(X[i+m], ilen, lt) && IsValidBranch(X[i+l], ilen, rt)) { - Xc.push_back(X[i+m] ^ X[i+l]); - Xc.back().TrimHash(clen); + Xc.emplace_back(X[i+m], X[i+l], clen); } } } @@ -406,8 +398,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState 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.push_back(Xt[i+l] ^ Xt[i+m]); - Xc.back().TrimHash(CollisionByteLength); + Xc.emplace_back(Xt[i+l], Xt[i+m], CollisionByteLength); } } @@ -443,7 +434,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState std::sort(Xt.begin(), Xt.end()); LogPrint("pow", "- Finding collisions\n"); for (int i = 0; i < Xt.size() - 1; i++) { - TruncatedStepRow res = Xt[i] ^ Xt[i+1]; + TruncatedStepRow res(Xt[i], Xt[i+1], 0); if (res.IsZero()) { partialSolns.push_back(res.GetPartialSolution(soln_size)); } @@ -548,8 +539,7 @@ bool Equihash::IsValidSolution(const eh_HashState& base_state, std::vector< 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], CollisionByteLength); } X = Xc; } diff --git a/src/crypto/equihash.h b/src/crypto/equihash.h index 4e067155d..22b8cc898 100644 --- a/src/crypto/equihash.h +++ b/src/crypto/equihash.h @@ -54,18 +54,12 @@ public: ~FullStepRow() { } FullStepRow(const FullStepRow& a) : StepRow {a}, indices(a.indices) { } + FullStepRow(const FullStepRow& a, const FullStepRow& b, int trim); FullStepRow& operator=(const FullStepRow& a); - FullStepRow& operator^=(const FullStepRow& a); - void TrimHash(int l); - bool IndicesBefore(const FullStepRow& a) { return indices[0] < a.indices[0]; } + inline bool IndicesBefore(const FullStepRow& a) const { return indices[0] < a.indices[0]; } std::vector GetSolution() { return std::vector(indices); } - friend inline const FullStepRow operator^(const FullStepRow& a, const FullStepRow& b) { - if (a.indices[0] < b.indices[0]) { return FullStepRow(a) ^= b; } - else { return FullStepRow(b) ^= a; } - } - friend bool DistinctIndices(const FullStepRow& a, const FullStepRow& b); friend bool IsValidBranch(const FullStepRow& a, const unsigned int ilen, const eh_trunc t); }; @@ -83,17 +77,11 @@ public: ~TruncatedStepRow() { } TruncatedStepRow(const TruncatedStepRow& a); + TruncatedStepRow(const TruncatedStepRow& a, const TruncatedStepRow& b, int trim); TruncatedStepRow& operator=(const TruncatedStepRow& a); - TruncatedStepRow& operator^=(const TruncatedStepRow& a); - void TrimHash(int l); inline bool IndicesBefore(const TruncatedStepRow& a) const { return memcmp(hash+len, a.hash+a.len, lenIndices) < 0; } eh_trunc* GetPartialSolution(eh_index soln_size) const; - - friend inline const TruncatedStepRow operator^(const TruncatedStepRow& a, const TruncatedStepRow& b) { - if (a.IndicesBefore(b)) { return TruncatedStepRow(a) ^= b; } - else { return TruncatedStepRow(b) ^= a; } - } }; template From 639c40047f074f424df1740522fd5c6031c133dc Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 6 May 2016 12:17:42 +1200 Subject: [PATCH 07/11] Use comparator object for sorting StepRows --- src/crypto/equihash.cpp | 18 +++++++++++++----- src/crypto/equihash.h | 16 +++++++++++++--- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/crypto/equihash.cpp b/src/crypto/equihash.cpp index e66e8e058..b03258025 100644 --- a/src/crypto/equihash.cpp +++ b/src/crypto/equihash.cpp @@ -227,6 +227,7 @@ std::set> Equihash::BasicSolve(const eh_HashState& ba // 1) Generate first list LogPrint("pow", "Generating first list\n"); + size_t hashLen = N/8; std::vector X; X.reserve(init_size); for (eh_index i = 0; i < init_size; i++) { @@ -238,7 +239,7 @@ std::set> Equihash::BasicSolve(const eh_HashState& ba 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; @@ -284,6 +285,8 @@ std::set> Equihash::BasicSolve(const eh_HashState& ba X.erase(X.begin()+posFree, X.end()); X.shrink_to_fit(); } + + hashLen -= CollisionByteLength; } // k+1) Find a collision on last 2n(k+1) bits @@ -291,7 +294,7 @@ std::set> Equihash::BasicSolve(const eh_HashState& ba std::set> 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++) { FullStepRow res(X[i], X[i+1], 0); @@ -369,6 +372,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState // 1) Generate first list LogPrint("pow", "Generating first list\n"); + size_t hashLen = N/8; std::vector Xt; Xt.reserve(init_size); for (eh_index i = 0; i < init_size; i++) { @@ -380,7 +384,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState LogPrint("pow", "Round %d:\n", r); // 2a) Sort the list LogPrint("pow", "- Sorting list\n"); - std::sort(Xt.begin(), Xt.end()); + std::sort(Xt.begin(), Xt.end(), CompareSR(hashLen)); LogPrint("pow", "- Finding collisions\n"); int i = 0; @@ -425,13 +429,15 @@ std::set> Equihash::OptimisedSolve(const eh_HashState Xt.erase(Xt.begin()+posFree, Xt.end()); Xt.shrink_to_fit(); } + + hashLen -= CollisionByteLength; } // 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()); + std::sort(Xt.begin(), Xt.end(), CompareSR(hashLen)); LogPrint("pow", "- Finding collisions\n"); for (int i = 0; i < Xt.size() - 1; i++) { TruncatedStepRow res(Xt[i], Xt[i+1], 0); @@ -453,6 +459,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState int invalidCount = 0; for (eh_trunc* partialSoln : partialSolns) { // 1) Generate first list of possibilities + size_t hashLen = N/8; std::vector> X; X.reserve(soln_size); for (eh_index i = 0; i < soln_size; i++) { @@ -476,7 +483,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState std::vector 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()); + std::sort(ic.begin(), ic.end(), CompareSR(hashLen)); CollideBranches(ic, CollisionByteLength, CollisionBitLength + 1, partialSoln[(1<> Equihash::OptimisedSolve(const eh_HashState } X = Xc; + hashLen -= CollisionByteLength; } // We are at the top of the tree diff --git a/src/crypto/equihash.h b/src/crypto/equihash.h index 22b8cc898..642c43d62 100644 --- a/src/crypto/equihash.h +++ b/src/crypto/equihash.h @@ -23,6 +23,8 @@ typedef uint8_t eh_trunc; class StepRow { + friend class CompareSR; + protected: unsigned char* hash; unsigned int len; @@ -36,12 +38,20 @@ public: bool IsZero(); std::string GetHex() { return HexStr(hash, hash+len); } - 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); }; +class CompareSR +{ +private: + size_t len; + +public: + CompareSR(size_t l) : len {l} { } + + inline bool operator()(const StepRow& a, const StepRow& b) { return memcmp(a.hash, b.hash, len) < 0; } +}; + bool HasCollision(StepRow& a, StepRow& b, int l); class FullStepRow : public StepRow From 29d9986c837bf6a1c3afc657261db49dc785f1bb Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 6 May 2016 14:13:15 +1200 Subject: [PATCH 08/11] Store full indices in the same char* as the hash --- src/crypto/equihash.cpp | 89 +++++++++++++++++++++++++++++++---------- src/crypto/equihash.h | 11 ++--- 2 files changed, 73 insertions(+), 27 deletions(-) diff --git a/src/crypto/equihash.cpp b/src/crypto/equihash.cpp index b03258025..1eb3dccf7 100644 --- a/src/crypto/equihash.cpp +++ b/src/crypto/equihash.cpp @@ -35,6 +35,28 @@ int Equihash::InitialiseState(eh_HashState& base_state) personalization); } +void EhIndexToArray(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(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(eh_index i, unsigned int ilen) { // Truncate to 8 bits @@ -72,44 +94,58 @@ StepRow::StepRow(const StepRow& a) : FullStepRow::FullStepRow(unsigned int n, const eh_HashState& base_state, eh_index i) : StepRow {n, base_state, i}, - indices {i} + lenIndices {sizeof(eh_index)} { - assert(indices.size() == 1); + unsigned char* p = new unsigned char[len+lenIndices]; + std::copy(hash, hash+len, p); + EhIndexToArray(i, p+len); + delete[] hash; + hash = p; +} + +FullStepRow::FullStepRow(const FullStepRow& a) : + StepRow {a}, + lenIndices {a.lenIndices} +{ + unsigned char* p = new unsigned char[a.len+a.lenIndices]; + std::copy(a.hash, a.hash+a.len+a.lenIndices, p); + delete[] hash; + hash = p; } FullStepRow::FullStepRow(const FullStepRow& a, const FullStepRow& b, int trim) : - StepRow {a} + StepRow {a}, + lenIndices {a.lenIndices+b.lenIndices} { if (a.len != b.len) { throw std::invalid_argument("Hash length differs"); } - if (a.indices.size() != b.indices.size()) { + if (a.lenIndices != b.lenIndices) { throw std::invalid_argument("Number of indices differs"); } - unsigned char* p = new unsigned char[a.len-trim]; + unsigned char* p = new unsigned char[a.len-trim+a.lenIndices+b.lenIndices]; for (int i = trim; i < a.len; i++) p[i-trim] = a.hash[i] ^ b.hash[i]; + len = a.len-trim; + if (a.IndicesBefore(b)) { + std::copy(a.hash+a.len, a.hash+a.len+a.lenIndices, p+len); + std::copy(b.hash+b.len, b.hash+b.len+b.lenIndices, p+len+a.lenIndices); + } else { + std::copy(b.hash+b.len, b.hash+b.len+b.lenIndices, p+len); + std::copy(a.hash+a.len, a.hash+a.len+a.lenIndices, p+len+b.lenIndices); + } delete[] hash; hash = p; - len = a.len-trim; - indices.reserve(a.indices.size() + b.indices.size()); - if (a.IndicesBefore(b)) { - indices.insert(indices.end(), a.indices.begin(), a.indices.end()); - indices.insert(indices.end(), b.indices.begin(), b.indices.end()); - } else { - indices.insert(indices.end(), b.indices.begin(), b.indices.end()); - indices.insert(indices.end(), a.indices.begin(), a.indices.end()); - } } FullStepRow& FullStepRow::operator=(const FullStepRow& a) { - unsigned char* p = new unsigned char[a.len]; - std::copy(a.hash, a.hash+a.len, p); + unsigned char* p = new unsigned char[a.len+a.lenIndices]; + std::copy(a.hash, a.hash+a.len+a.lenIndices, p); delete[] hash; hash = p; len = a.len; - indices = a.indices; + lenIndices = a.lenIndices; return *this; } @@ -121,6 +157,15 @@ bool StepRow::IsZero() return res == 0; } +std::vector FullStepRow::GetIndices() const +{ + std::vector ret; + for (int i = 0; i < lenIndices; i += sizeof(eh_index)) { + ret.push_back(ArrayToEhIndex(hash+len+i)); + } + return ret; +} + bool HasCollision(StepRow& a, StepRow& b, int l) { bool res = true; @@ -132,8 +177,8 @@ bool HasCollision(StepRow& a, StepRow& b, int l) // Checks if the intersection of a.indices and b.indices is empty bool DistinctIndices(const FullStepRow& a, const FullStepRow& b) { - std::vector aSrt(a.indices); - std::vector bSrt(b.indices); + std::vector aSrt = a.GetIndices(); + std::vector bSrt = b.GetIndices(); std::sort(aSrt.begin(), aSrt.end()); std::sort(bSrt.begin(), bSrt.end()); @@ -152,7 +197,7 @@ bool DistinctIndices(const FullStepRow& a, const FullStepRow& b) bool IsValidBranch(const FullStepRow& a, const unsigned int ilen, const eh_trunc t) { - return TruncateIndex(a.indices[0], ilen) == t; + return TruncateIndex(ArrayToEhIndex(a.hash+a.len), ilen) == t; } TruncatedStepRow::TruncatedStepRow(unsigned int n, const eh_HashState& base_state, eh_index i, unsigned int ilen) : @@ -299,7 +344,7 @@ std::set> Equihash::BasicSolve(const eh_HashState& ba for (int i = 0; i < X.size() - 1; i++) { FullStepRow res(X[i], X[i+1], 0); if (res.IsZero() && DistinctIndices(X[i], X[i+1])) { - solns.insert(res.GetSolution()); + solns.insert(res.GetIndices()); } } } else @@ -500,7 +545,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState // We are at the top of the tree assert(X.size() == 1); for (FullStepRow row : X[0]) { - solns.insert(row.GetSolution()); + solns.insert(row.GetIndices()); } goto deletesolution; diff --git a/src/crypto/equihash.h b/src/crypto/equihash.h index 642c43d62..a1c390d1c 100644 --- a/src/crypto/equihash.h +++ b/src/crypto/equihash.h @@ -21,6 +21,8 @@ typedef crypto_generichash_blake2b_state eh_HashState; typedef uint32_t eh_index; typedef uint8_t eh_trunc; +eh_index ArrayToEhIndex(unsigned char* array); + class StepRow { friend class CompareSR; @@ -57,20 +59,19 @@ bool HasCollision(StepRow& a, StepRow& b, int l); class FullStepRow : public StepRow { private: - std::vector indices; + unsigned int lenIndices; public: FullStepRow(unsigned int n, const eh_HashState& base_state, eh_index i); ~FullStepRow() { } - FullStepRow(const FullStepRow& a) : StepRow {a}, indices(a.indices) { } + FullStepRow(const FullStepRow& a); FullStepRow(const FullStepRow& a, const FullStepRow& b, int trim); FullStepRow& operator=(const FullStepRow& a); - inline bool IndicesBefore(const FullStepRow& a) const { return indices[0] < a.indices[0]; } - std::vector GetSolution() { return std::vector(indices); } + inline bool IndicesBefore(const FullStepRow& a) const { return ArrayToEhIndex(hash+len) < ArrayToEhIndex(a.hash+a.len); } + std::vector GetIndices() const; - friend bool DistinctIndices(const FullStepRow& a, const FullStepRow& b); friend bool IsValidBranch(const FullStepRow& a, const unsigned int ilen, const eh_trunc t); }; From d4d76536a5cd4446bb89806e1d88cd506f726f08 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sat, 7 May 2016 16:36:54 +1200 Subject: [PATCH 09/11] Use fixed-width array for storing hash and indices --- src/crypto/equihash.cpp | 274 +++++++++++++++------------------------- src/crypto/equihash.h | 79 +++++++----- src/crypto/equihash.tcc | 35 +++++ 3 files changed, 186 insertions(+), 202 deletions(-) create mode 100644 src/crypto/equihash.tcc diff --git a/src/crypto/equihash.cpp b/src/crypto/equihash.cpp index 1eb3dccf7..4694d2240 100644 --- a/src/crypto/equihash.cpp +++ b/src/crypto/equihash.cpp @@ -35,7 +35,7 @@ int Equihash::InitialiseState(eh_HashState& base_state) personalization); } -void EhIndexToArray(eh_index i, unsigned char* array) +void EhIndexToArray(const eh_index i, unsigned char* array) { assert(sizeof(eh_index) == 4); array[0] = (i >> 24) & 0xFF; @@ -44,7 +44,7 @@ void EhIndexToArray(eh_index i, unsigned char* array) array[3] = i & 0xFF; } -eh_index ArrayToEhIndex(unsigned char* array) +eh_index ArrayToEhIndex(const unsigned char* array) { assert(sizeof(eh_index) == 4); eh_index ret {array[0]}; @@ -57,22 +57,21 @@ eh_index ArrayToEhIndex(unsigned char* array) return ret; } -eh_trunc TruncateIndex(eh_index i, unsigned int ilen) +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(eh_trunc t, eh_index r, unsigned int ilen) +eh_index UntruncateIndex(const eh_trunc t, const eh_index r, const unsigned int ilen) { eh_index i{t}; return (i << (ilen - 8)) | r; } -StepRow::StepRow(unsigned int n, const eh_HashState& base_state, eh_index i) : - hash {new unsigned char[n/8]}, - len {n/8} +template +StepRow::StepRow(unsigned int n, const eh_HashState& base_state, eh_index i) { eh_HashState state; state = base_state; @@ -80,76 +79,46 @@ StepRow::StepRow(unsigned int n, const eh_HashState& base_state, eh_index i) : crypto_generichash_blake2b_final(&state, hash, n/8); } -StepRow::~StepRow() +template template +StepRow::StepRow(const StepRow& 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} +template +FullStepRow::FullStepRow(unsigned int n, const eh_HashState& base_state, eh_index i) : + StepRow {n, base_state, i} { - std::copy(a.hash, a.hash+a.len, hash); + EhIndexToArray(i, hash+(n/8)); } -FullStepRow::FullStepRow(unsigned int n, const eh_HashState& base_state, eh_index i) : - StepRow {n, base_state, i}, - lenIndices {sizeof(eh_index)} +template template +FullStepRow::FullStepRow(const FullStepRow& a, const FullStepRow& b, size_t len, size_t lenIndices, int trim) : + StepRow {a} { - unsigned char* p = new unsigned char[len+lenIndices]; - std::copy(hash, hash+len, p); - EhIndexToArray(i, p+len); - delete[] hash; - hash = p; -} - -FullStepRow::FullStepRow(const FullStepRow& a) : - StepRow {a}, - lenIndices {a.lenIndices} -{ - unsigned char* p = new unsigned char[a.len+a.lenIndices]; - std::copy(a.hash, a.hash+a.len+a.lenIndices, p); - delete[] hash; - hash = p; -} - -FullStepRow::FullStepRow(const FullStepRow& a, const FullStepRow& b, int trim) : - StepRow {a}, - lenIndices {a.lenIndices+b.lenIndices} -{ - if (a.len != b.len) { - throw std::invalid_argument("Hash length differs"); - } - if (a.lenIndices != b.lenIndices) { - throw std::invalid_argument("Number of indices differs"); - } - unsigned char* p = new unsigned char[a.len-trim+a.lenIndices+b.lenIndices]; - for (int i = trim; i < a.len; i++) - p[i-trim] = a.hash[i] ^ b.hash[i]; - len = a.len-trim; - if (a.IndicesBefore(b)) { - std::copy(a.hash+a.len, a.hash+a.len+a.lenIndices, p+len); - std::copy(b.hash+b.len, b.hash+b.len+b.lenIndices, p+len+a.lenIndices); + 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+b.len, b.hash+b.len+b.lenIndices, p+len); - std::copy(a.hash+a.len, a.hash+a.len+a.lenIndices, p+len+b.lenIndices); + 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); } - delete[] hash; - hash = p; } -FullStepRow& FullStepRow::operator=(const FullStepRow& a) +template +FullStepRow& FullStepRow::operator=(const FullStepRow& a) { - unsigned char* p = new unsigned char[a.len+a.lenIndices]; - std::copy(a.hash, a.hash+a.len+a.lenIndices, p); - delete[] hash; - hash = p; - len = a.len; - lenIndices = a.lenIndices; + std::copy(a.hash, a.hash+WIDTH, hash); return *this; } -bool StepRow::IsZero() +template +bool StepRow::IsZero(size_t len) { char res = 0; for (int i = 0; i < len; i++) @@ -157,7 +126,8 @@ bool StepRow::IsZero() return res == 0; } -std::vector FullStepRow::GetIndices() const +template +std::vector FullStepRow::GetIndices(size_t len, size_t lenIndices) const { std::vector ret; for (int i = 0; i < lenIndices; i += sizeof(eh_index)) { @@ -166,7 +136,8 @@ std::vector FullStepRow::GetIndices() const return ret; } -bool HasCollision(StepRow& a, StepRow& b, int l) +template +bool HasCollision(StepRow& a, StepRow& b, int l) { bool res = true; for (int j = 0; j < l; j++) @@ -174,92 +145,40 @@ bool HasCollision(StepRow& a, StepRow& b, int l) return res; } -// Checks if the intersection of a.indices and b.indices is empty -bool DistinctIndices(const FullStepRow& a, const FullStepRow& b) +template +TruncatedStepRow::TruncatedStepRow(unsigned int n, const eh_HashState& base_state, eh_index i, unsigned int ilen) : + StepRow {n, base_state, i} { - std::vector aSrt = a.GetIndices(); - std::vector bSrt = b.GetIndices(); - - 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; + hash[n/8] = TruncateIndex(i, ilen); } -bool IsValidBranch(const FullStepRow& a, const unsigned int ilen, const eh_trunc t) +template template +TruncatedStepRow::TruncatedStepRow(const TruncatedStepRow& a, const TruncatedStepRow& b, size_t len, size_t lenIndices, int trim) : + StepRow {a} { - return TruncateIndex(ArrayToEhIndex(a.hash+a.len), ilen) == t; -} - -TruncatedStepRow::TruncatedStepRow(unsigned int n, const eh_HashState& base_state, eh_index i, unsigned int ilen) : - StepRow {n, base_state, i}, - lenIndices {1} -{ - unsigned char* p = new unsigned char[len+lenIndices]; - std::copy(hash, hash+len, p); - p[len] = TruncateIndex(i, ilen); - delete[] hash; - hash = p; -} - -TruncatedStepRow::TruncatedStepRow(const TruncatedStepRow& a) : - StepRow {a}, - lenIndices {a.lenIndices} -{ - unsigned char* p = new unsigned char[a.len+a.lenIndices]; - std::copy(a.hash, a.hash+a.len+a.lenIndices, p); - delete[] hash; - hash = p; -} - -TruncatedStepRow::TruncatedStepRow(const TruncatedStepRow& a, const TruncatedStepRow& b, int trim) : - StepRow {a}, - lenIndices {a.lenIndices+b.lenIndices} -{ - if (a.len != b.len) { - throw std::invalid_argument("Hash length differs"); - } - if (a.lenIndices != b.lenIndices) { - throw std::invalid_argument("Number of indices differs"); - } - unsigned char* p = new unsigned char[a.len-trim+a.lenIndices+b.lenIndices]; - for (int i = trim; i < a.len; i++) - p[i-trim] = a.hash[i] ^ b.hash[i]; - len = a.len-trim; - if (a.IndicesBefore(b)) { - std::copy(a.hash+a.len, a.hash+a.len+a.lenIndices, p+len); - std::copy(b.hash+b.len, b.hash+b.len+b.lenIndices, p+len+a.lenIndices); + 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+b.len, b.hash+b.len+b.lenIndices, p+len); - std::copy(a.hash+a.len, a.hash+a.len+a.lenIndices, p+len+b.lenIndices); + 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); } - delete[] hash; - hash = p; } -TruncatedStepRow& TruncatedStepRow::operator=(const TruncatedStepRow& a) +template +TruncatedStepRow& TruncatedStepRow::operator=(const TruncatedStepRow& a) { - unsigned char* p = new unsigned char[a.len+a.lenIndices]; - std::copy(a.hash, a.hash+a.len+a.lenIndices, p); - delete[] hash; - hash = p; - len = a.len; - lenIndices = a.lenIndices; + std::copy(a.hash, a.hash+WIDTH, hash); return *this; } -eh_trunc* TruncatedStepRow::GetPartialSolution(eh_index soln_size) const +template +eh_trunc* TruncatedStepRow::GetTruncatedIndices(size_t len, size_t lenIndices) const { - assert(lenIndices == soln_size); eh_trunc* p = new eh_trunc[lenIndices]; std::copy(hash+len, hash+len+lenIndices, p); return p; @@ -273,7 +192,8 @@ std::set> Equihash::BasicSolve(const eh_HashState& ba // 1) Generate first list LogPrint("pow", "Generating first list\n"); size_t hashLen = N/8; - std::vector X; + size_t lenIndices = sizeof(eh_index); + std::vector> X; X.reserve(init_size); for (eh_index i = 0; i < init_size; i++) { X.emplace_back(N, base_state, i); @@ -289,7 +209,7 @@ std::set> Equihash::BasicSolve(const eh_HashState& ba LogPrint("pow", "- Finding collisions\n"); int i = 0; int posFree = 0; - std::vector Xc; + std::vector> 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; @@ -301,8 +221,8 @@ std::set> Equihash::BasicSolve(const eh_HashState& ba // 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.emplace_back(X[i+l], X[i+m], CollisionByteLength); + if (DistinctIndices(X[i+l], X[i+m], hashLen, lenIndices)) { + Xc.emplace_back(X[i+l], X[i+m], hashLen, lenIndices, CollisionByteLength); } } } @@ -332,6 +252,7 @@ std::set> Equihash::BasicSolve(const eh_HashState& ba } hashLen -= CollisionByteLength; + lenIndices *= 2; } // k+1) Find a collision on last 2n(k+1) bits @@ -342,9 +263,9 @@ std::set> Equihash::BasicSolve(const eh_HashState& ba std::sort(X.begin(), X.end(), CompareSR(hashLen)); LogPrint("pow", "- Finding collisions\n"); for (int i = 0; i < X.size() - 1; i++) { - FullStepRow res(X[i], X[i+1], 0); - if (res.IsZero() && DistinctIndices(X[i], X[i+1])) { - solns.insert(res.GetIndices()); + FullStepRow 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 @@ -353,11 +274,12 @@ std::set> Equihash::BasicSolve(const eh_HashState& ba return solns; } -void CollideBranches(std::vector& X, const unsigned int clen, const unsigned int ilen, const eh_trunc lt, const eh_trunc rt) +template +void CollideBranches(std::vector>& 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 Xc; + std::vector> 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; @@ -369,11 +291,11 @@ void CollideBranches(std::vector& X, const unsigned int clen, const // 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])) { - if (IsValidBranch(X[i+l], ilen, lt) && IsValidBranch(X[i+m], ilen, rt)) { - Xc.emplace_back(X[i+l], X[i+m], clen); - } else if (IsValidBranch(X[i+m], ilen, lt) && IsValidBranch(X[i+l], ilen, rt)) { - Xc.emplace_back(X[i+m], X[i+l], clen); + 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); } } } @@ -418,7 +340,8 @@ std::set> Equihash::OptimisedSolve(const eh_HashState // 1) Generate first list LogPrint("pow", "Generating first list\n"); size_t hashLen = N/8; - std::vector Xt; + size_t lenIndices = sizeof(eh_trunc); + std::vector> Xt; Xt.reserve(init_size); for (eh_index i = 0; i < init_size; i++) { Xt.emplace_back(N, base_state, i, CollisionBitLength + 1); @@ -434,7 +357,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState LogPrint("pow", "- Finding collisions\n"); int i = 0; int posFree = 0; - std::vector Xc; + std::vector> 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; @@ -447,7 +370,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState 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], CollisionByteLength); + Xc.emplace_back(Xt[i+l], Xt[i+m], hashLen, lenIndices, CollisionByteLength); } } @@ -476,6 +399,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState } hashLen -= CollisionByteLength; + lenIndices *= 2; } // k+1) Find a collision on last 2n(k+1) bits @@ -485,9 +409,9 @@ std::set> Equihash::OptimisedSolve(const eh_HashState std::sort(Xt.begin(), Xt.end(), CompareSR(hashLen)); LogPrint("pow", "- Finding collisions\n"); for (int i = 0; i < Xt.size() - 1; i++) { - TruncatedStepRow res(Xt[i], Xt[i+1], 0); - if (res.IsZero()) { - partialSolns.push_back(res.GetPartialSolution(soln_size)); + TruncatedStepRow res(Xt[i], Xt[i+1], hashLen, lenIndices, 0); + if (res.IsZero(hashLen)) { + partialSolns.push_back(res.GetTruncatedIndices(hashLen, 2*lenIndices)); } } } else @@ -505,10 +429,11 @@ std::set> Equihash::OptimisedSolve(const eh_HashState for (eh_trunc* partialSoln : partialSolns) { // 1) Generate first list of possibilities size_t hashLen = N/8; - std::vector> X; + size_t lenIndices = sizeof(eh_index); + std::vector>> X; X.reserve(soln_size); for (eh_index i = 0; i < soln_size; i++) { - std::vector ic; + std::vector> ic; ic.reserve(recreate_size); for (eh_index j = 0; j < recreate_size; j++) { eh_index newIndex { UntruncateIndex(partialSoln[i], j, CollisionBitLength + 1) }; @@ -519,17 +444,17 @@ std::set> Equihash::OptimisedSolve(const eh_HashState // 3) Repeat step 2 for each level of the tree for (int r = 0; X.size() > 1; r++) { - std::vector> Xc; + std::vector>> 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 ic(X[v]); + std::vector> 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, CollisionByteLength, CollisionBitLength + 1, partialSoln[(1<> Equihash::OptimisedSolve(const eh_HashState X = Xc; hashLen -= CollisionByteLength; + lenIndices *= 2; } // We are at the top of the tree assert(X.size() == 1); - for (FullStepRow row : X[0]) { - solns.insert(row.GetIndices()); + for (FullStepRow row : X[0]) { + solns.insert(row.GetIndices(hashLen, lenIndices)); } goto deletesolution; @@ -569,36 +495,40 @@ bool Equihash::IsValidSolution(const eh_HashState& base_state, std::vector< return false; } - std::vector X; + std::vector> X; X.reserve(soln_size); for (eh_index i : soln) { X.emplace_back(N, base_state, i); } + size_t hashLen = N/8; + size_t lenIndices = sizeof(eh_index); while (X.size() > 1) { - std::vector Xc; + std::vector> Xc; for (int i = 0; i < X.size(); i += 2) { 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.emplace_back(X[i], X[i+1], 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> diff --git a/src/crypto/equihash.h b/src/crypto/equihash.h index a1c390d1c..20952cb6a 100644 --- a/src/crypto/equihash.h +++ b/src/crypto/equihash.h @@ -21,26 +21,31 @@ typedef crypto_generichash_blake2b_state eh_HashState; typedef uint32_t eh_index; typedef uint8_t eh_trunc; -eh_index ArrayToEhIndex(unsigned char* array); +eh_index ArrayToEhIndex(const unsigned char* array); +eh_trunc TruncateIndex(const eh_index i, const unsigned int ilen); +template class StepRow { + template + friend class StepRow; friend class CompareSR; protected: - unsigned char* hash; - unsigned int len; + unsigned char hash[WIDTH]; public: StepRow(unsigned int n, const eh_HashState& base_state, eh_index i); - ~StepRow(); + ~StepRow() { } - StepRow(const StepRow& a); + template + StepRow(const StepRow& a); - bool IsZero(); - 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 bool HasCollision(StepRow& a, StepRow& b, int l); + template + friend bool HasCollision(StepRow& a, StepRow& b, int l); }; class CompareSR @@ -51,48 +56,56 @@ private: public: CompareSR(size_t l) : len {l} { } - inline bool operator()(const StepRow& a, const StepRow& b) { return memcmp(a.hash, b.hash, len) < 0; } + template + inline bool operator()(const StepRow& a, const StepRow& b) { return memcmp(a.hash, b.hash, len) < 0; } }; -bool HasCollision(StepRow& a, StepRow& b, int l); +template +bool HasCollision(StepRow& a, StepRow& b, int l); -class FullStepRow : public StepRow +template +class FullStepRow : public StepRow { -private: - unsigned int lenIndices; + template + friend class FullStepRow; + + using StepRow::hash; public: FullStepRow(unsigned int n, const eh_HashState& base_state, eh_index i); ~FullStepRow() { } - FullStepRow(const FullStepRow& a); - FullStepRow(const FullStepRow& a, const FullStepRow& b, int trim); - FullStepRow& operator=(const FullStepRow& a); + FullStepRow(const FullStepRow& a) : StepRow {a} { } + template + FullStepRow(const FullStepRow& a, const FullStepRow& b, size_t len, size_t lenIndices, int trim); + FullStepRow& operator=(const FullStepRow& a); - inline bool IndicesBefore(const FullStepRow& a) const { return ArrayToEhIndex(hash+len) < ArrayToEhIndex(a.hash+a.len); } - std::vector GetIndices() const; + inline bool IndicesBefore(const FullStepRow& a, size_t len) const { return ArrayToEhIndex(hash+len) < ArrayToEhIndex(a.hash+len); } + std::vector GetIndices(size_t len, size_t lenIndices) const; - friend bool IsValidBranch(const FullStepRow& a, const unsigned int ilen, const eh_trunc t); + template + friend bool IsValidBranch(const FullStepRow& a, const size_t len, const unsigned int ilen, const eh_trunc t); }; -bool DistinctIndices(const FullStepRow& a, const FullStepRow& b); -bool IsValidBranch(const FullStepRow& a, const unsigned int ilen, const eh_trunc t); - -class TruncatedStepRow : public StepRow +template +class TruncatedStepRow : public StepRow { -private: - unsigned int lenIndices; + template + friend class TruncatedStepRow; + + using StepRow::hash; public: TruncatedStepRow(unsigned int n, const eh_HashState& base_state, eh_index i, unsigned int ilen); ~TruncatedStepRow() { } - TruncatedStepRow(const TruncatedStepRow& a); - TruncatedStepRow(const TruncatedStepRow& a, const TruncatedStepRow& b, int trim); - TruncatedStepRow& operator=(const TruncatedStepRow& a); + TruncatedStepRow(const TruncatedStepRow& a) : StepRow {a} { } + template + TruncatedStepRow(const TruncatedStepRow& a, const TruncatedStepRow& b, size_t len, size_t lenIndices, int trim); + TruncatedStepRow& operator=(const TruncatedStepRow& a); - inline bool IndicesBefore(const TruncatedStepRow& a) const { return memcmp(hash+len, a.hash+a.len, lenIndices) < 0; } - eh_trunc* GetPartialSolution(eh_index soln_size) const; + inline bool IndicesBefore(const TruncatedStepRow& 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 @@ -107,6 +120,10 @@ private: public: 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)) }; Equihash() { } @@ -116,6 +133,8 @@ public: bool IsValidSolution(const eh_HashState& base_state, std::vector soln); }; +#include "equihash.tcc" + static Equihash<96,5> Eh965; static Equihash<48,5> Eh485; diff --git a/src/crypto/equihash.tcc b/src/crypto/equihash.tcc new file mode 100644 index 000000000..e3ad9858c --- /dev/null +++ b/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 +#include + +// Checks if the intersection of a.indices and b.indices is empty +template +bool DistinctIndices(const FullStepRow& a, const FullStepRow& b, size_t len, size_t lenIndices) +{ + std::vector aSrt = a.GetIndices(len, lenIndices); + std::vector 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 +bool IsValidBranch(const FullStepRow& a, const size_t len, const unsigned int ilen, const eh_trunc t) +{ + return TruncateIndex(ArrayToEhIndex(a.hash+len), ilen) == t; +} From b5c6a3af125c6aea72b622f3530a5ccb3423a9b8 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Tue, 24 May 2016 12:08:13 +1200 Subject: [PATCH 10/11] Use optimised Equihash solver for miner and benchmarks The basic solver is still used for regtest-only purposes. --- src/miner.cpp | 2 +- src/zcbenchmarks.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/miner.cpp b/src/miner.cpp index e3d89d3a8..4770abd04 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -514,7 +514,7 @@ void static BitcoinMiner(CWallet *pwallet) LogPrint("pow", "Running Equihash solver with nNonce = %s\n", pblock->nNonce.ToString()); std::set> solns; - EhBasicSolve(n, k, curr_state, solns); + EhOptimisedSolve(n, k, curr_state, solns); LogPrint("pow", "Solutions: %d\n", solns.size()); // Write the solution to the hash and compute the result. diff --git a/src/zcbenchmarks.cpp b/src/zcbenchmarks.cpp index 70028917c..751a80f44 100644 --- a/src/zcbenchmarks.cpp +++ b/src/zcbenchmarks.cpp @@ -114,7 +114,7 @@ double benchmark_solve_equihash() timer_start(); std::set> solns; - EhBasicSolve(n, k, eh_state, solns); + EhOptimisedSolve(n, k, eh_state, solns); return timer_stop(); } From 447444ae7a3ed4e13bfb09e0c15d458070e9b904 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 1 Jun 2016 23:15:04 +1200 Subject: [PATCH 11/11] Fix nits after review --- src/crypto/equihash.cpp | 22 ++++++++++------- src/crypto/equihash.h | 52 ++++++++++++++++++++--------------------- 2 files changed, 40 insertions(+), 34 deletions(-) diff --git a/src/crypto/equihash.cpp b/src/crypto/equihash.cpp index 4694d2240..b31cf0461 100644 --- a/src/crypto/equihash.cpp +++ b/src/crypto/equihash.cpp @@ -120,10 +120,12 @@ FullStepRow& FullStepRow::operator=(const FullStepRow& a) template bool StepRow::IsZero(size_t len) { - 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 i = 0; i < len; i++) { + if (hash[i] != 0) + return false; + } + return true; } template @@ -139,10 +141,12 @@ std::vector FullStepRow::GetIndices(size_t len, size_t lenIndic template 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; + // 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 @@ -334,6 +338,8 @@ std::set> Equihash::OptimisedSolve(const eh_HashState // 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 partialSolns; { diff --git a/src/crypto/equihash.h b/src/crypto/equihash.h index 20952cb6a..373daf46a 100644 --- a/src/crypto/equihash.h +++ b/src/crypto/equihash.h @@ -135,42 +135,42 @@ public: #include "equihash.tcc" -static Equihash<96,5> Eh965; -static Equihash<48,5> Eh485; +static Equihash<96,5> Eh96_5; +static Equihash<48,5> Eh48_5; -#define EhInitialiseState(n, k, base_state) \ - if (n == 96 && k == 5) { \ - Eh965.InitialiseState(base_state); \ - } else if (n == 48 && k == 5) { \ - Eh485.InitialiseState(base_state); \ - } else { \ +#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 = Eh965.BasicSolve(base_state); \ - } else if (n == 48 && k == 5) { \ - solns = Eh485.BasicSolve(base_state); \ - } else { \ +#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 = Eh965.OptimisedSolve(base_state); \ - } else if (n == 48 && k == 5) { \ - solns = Eh485.OptimisedSolve(base_state); \ - } else { \ +#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 = Eh965.IsValidSolution(base_state, soln); \ - } else if (n == 48 && k == 5) { \ - ret = Eh485.IsValidSolution(base_state, soln); \ - } else { \ +#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"); \ }