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(); }