Use template parameters to statically initialise Equihash

This commit is contained in:
Jack Grigg 2016-05-05 15:00:44 +12:00
parent 39f5cb35f9
commit e95747288a
7 changed files with 124 additions and 75 deletions

View File

@ -19,31 +19,18 @@
#include <iostream>
#include <stdexcept>
void validate_params(int n, int k)
{
if (k>=n) {
std::cerr << "n must be larger than k\n";
throw invalid_params();
}
if (n % 8 != 0) {
std::cerr << "Parameters must satisfy n = 0 mod 8\n";
throw invalid_params();
}
if ((n/(k+1)) % 8 != 0) {
std::cerr << "Parameters must satisfy n/(k+1) = 0 mod 8\n";
throw invalid_params();
}
}
int Equihash::InitialiseState(eh_HashState& base_state)
template<unsigned int N, unsigned int K>
int Equihash<N,K>::InitialiseState(eh_HashState& base_state)
{
unsigned int n = N;
unsigned int k = K;
unsigned char personalization[crypto_generichash_blake2b_PERSONALBYTES] = {};
memcpy(personalization, "ZcashPOW", 8);
memcpy(personalization+8, &n, 4);
memcpy(personalization+12, &k, 4);
return crypto_generichash_blake2b_init_salt_personal(&base_state,
NULL, 0, // No key.
n/8,
N/8,
NULL, // No salt.
personalization);
}
@ -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<unsigned int N, unsigned int K>
std::set<std::vector<eh_index>> Equihash<N,K>::BasicSolve(const eh_HashState& base_state)
{
validate_params(n, k);
}
std::set<std::vector<eh_index>> 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<FullStepRow> 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<std::vector<eh_index>> 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<std::vector<eh_index>> 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<FullStepRow>& X, const unsigned int clen, const
}
}
std::set<std::vector<eh_index>> Equihash::OptimisedSolve(const eh_HashState& base_state)
template<unsigned int N, unsigned int K>
std::set<std::vector<eh_index>> Equihash<N,K>::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<eh_trunc*> partialSolns;
{
@ -399,11 +380,11 @@ std::set<std::vector<eh_index>> Equihash::OptimisedSolve(const eh_HashState& bas
std::vector<TruncatedStepRow> 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<std::vector<eh_index>> 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<std::vector<eh_index>> 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<std::vector<eh_index>> 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<std::vector<eh_index>> 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<std::vector<eh_index>> Equihash::OptimisedSolve(const eh_HashState& bas
std::vector<FullStepRow> 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<std::vector<eh_index>> 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<<r)*v], partialSoln[(1<<r)*(v+1)]);
CollideBranches(ic, CollisionByteLength, CollisionBitLength + 1, partialSoln[(1<<r)*v], partialSoln[(1<<r)*(v+1)]);
// 2v) Check if this has become an invalid solution
if (ic.size() == 0)
@ -535,9 +516,10 @@ deletesolution:
return solns;
}
bool Equihash::IsValidSolution(const eh_HashState& base_state, std::vector<eh_index> soln)
template<unsigned int N, unsigned int K>
bool Equihash<N,K>::IsValidSolution(const eh_HashState& base_state, std::vector<eh_index> soln)
{
eh_index soln_size { 1u << k };
eh_index soln_size { 1u << K };
if (soln.size() != soln_size) {
LogPrint("pow", "Invalid solution size: %d\n", soln.size());
return false;
@ -546,13 +528,13 @@ bool Equihash::IsValidSolution(const eh_HashState& base_state, std::vector<eh_in
std::vector<FullStepRow> 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<FullStepRow> 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<eh_in
return false;
}
Xc.push_back(X[i] ^ X[i+1]);
Xc.back().TrimHash(CollisionByteLength());
Xc.back().TrimHash(CollisionByteLength);
}
X = Xc;
}
@ -575,3 +557,15 @@ bool Equihash::IsValidSolution(const eh_HashState& base_state, std::vector<eh_in
assert(X.size() == 1);
return X[0].IsZero();
}
// Explicit instantiations for Equihash<96,5>
template int Equihash<96,5>::InitialiseState(eh_HashState& base_state);
template std::set<std::vector<eh_index>> Equihash<96,5>::BasicSolve(const eh_HashState& base_state);
template std::set<std::vector<eh_index>> Equihash<96,5>::OptimisedSolve(const eh_HashState& base_state);
template bool Equihash<96,5>::IsValidSolution(const eh_HashState& base_state, std::vector<eh_index> soln);
// Explicit instantiations for Equihash<48,5>
template int Equihash<48,5>::InitialiseState(eh_HashState& base_state);
template std::set<std::vector<eh_index>> Equihash<48,5>::BasicSolve(const eh_HashState& base_state);
template std::set<std::vector<eh_index>> Equihash<48,5>::OptimisedSolve(const eh_HashState& base_state);
template bool Equihash<48,5>::IsValidSolution(const eh_HashState& base_state, std::vector<eh_index> soln);

View File

@ -15,12 +15,12 @@
#include <set>
#include <vector>
#include <boost/static_assert.hpp>
typedef crypto_generichash_blake2b_state eh_HashState;
typedef uint32_t eh_index;
typedef uint8_t eh_trunc;
struct invalid_params { };
class StepRow
{
protected:
@ -96,17 +96,20 @@ public:
}
};
template<unsigned int N, unsigned int K>
class Equihash
{
private:
unsigned int n;
unsigned int k;
BOOST_STATIC_ASSERT(K < N);
BOOST_STATIC_ASSERT(N % 8 == 0);
BOOST_STATIC_ASSERT((N/(K+1)) % 8 == 0);
BOOST_STATIC_ASSERT((N/(K+1)) + 1 < 8*sizeof(eh_index));
public:
Equihash(unsigned int n, unsigned int k);
enum { CollisionBitLength=N/(K+1) };
enum { CollisionByteLength=CollisionBitLength/8 };
inline unsigned int CollisionBitLength() { return n/(k+1); }
inline unsigned int CollisionByteLength() { return CollisionBitLength()/8; }
Equihash() { }
int InitialiseState(eh_HashState& base_state);
std::set<std::vector<eh_index>> BasicSolve(const eh_HashState& base_state);
@ -114,4 +117,43 @@ public:
bool IsValidSolution(const eh_HashState& base_state, std::vector<eh_index> 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

View File

@ -443,7 +443,8 @@ void static BitcoinMiner(CWallet *pwallet)
CReserveKey reservekey(pwallet);
unsigned int nExtraNonce = 0;
Equihash eh {chainparams.EquihashN(), chainparams.EquihashK()};
unsigned int n = chainparams.EquihashN();
unsigned int k = chainparams.EquihashK();
try {
while (true) {
@ -489,7 +490,7 @@ void static BitcoinMiner(CWallet *pwallet)
while (true) {
// Hash state
crypto_generichash_blake2b_state state;
eh.InitialiseState(state);
EhInitialiseState(n, k, state);
// I = the block header minus nonce and solution.
CEquihashInput I{*pblock};
@ -512,7 +513,8 @@ void static BitcoinMiner(CWallet *pwallet)
// (x_1, x_2, ...) = A(I, V, n, k)
LogPrint("pow", "Running Equihash solver with nNonce = %s\n",
pblock->nNonce.ToString());
std::set<std::vector<unsigned int>> solns = eh.BasicSolve(curr_state);
std::set<std::vector<unsigned int>> solns;
EhBasicSolve(n, k, curr_state, solns);
LogPrint("pow", "Solutions: %d\n", solns.size());
// Write the solution to the hash and compute the result.

View File

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

View File

@ -150,7 +150,8 @@ Value generate(const Array& params, bool fHelp)
}
unsigned int nExtraNonce = 0;
Array blockHashes;
Equihash eh {Params().EquihashN(), Params().EquihashK()};
unsigned int n = Params().EquihashN();
unsigned int k = Params().EquihashK();
while (nHeight < nHeightEnd)
{
auto_ptr<CBlockTemplate> pblocktemplate(CreateNewBlockWithKey(reservekey));
@ -164,7 +165,7 @@ Value generate(const Array& params, bool fHelp)
// Hash state
crypto_generichash_blake2b_state eh_state;
eh.InitialiseState(eh_state);
EhInitialiseState(n, k, eh_state);
// I = the block header minus nonce and solution.
CEquihashInput I{*pblock};
@ -187,10 +188,13 @@ Value generate(const Array& params, bool fHelp)
pblock->nNonce.size());
// (x_1, x_2, ...) = A(I, V, n, k)
std::set<std::vector<unsigned int>> solns = eh.BasicSolve(curr_state);
std::set<std::vector<unsigned int>> solns;
EhBasicSolve(n, k, curr_state, solns);
for (auto soln : solns) {
assert(eh.IsValidSolution(curr_state, soln));
bool isValid;
EhIsValidSolution(n, k, curr_state, soln, isValid);
assert(isValid);
pblock->nSolution = soln;
if (CheckProofOfWork(pblock->GetHash(), pblock->nBits, Params().GetConsensus())) {

View File

@ -41,16 +41,16 @@ void PrintSolutions(std::stringstream &strm, std::set<std::vector<uint32_t>> sol
}
void TestEquihashSolvers(unsigned int n, unsigned int k, const std::string &I, const arith_uint256 &nonce, const std::set<std::vector<uint32_t>> &solns) {
Equihash eh {n, k};
crypto_generichash_blake2b_state state;
eh.InitialiseState(state);
EhInitialiseState(n, k, state);
uint256 V = ArithToUint256(nonce);
BOOST_TEST_MESSAGE("Running solver: n = " << n << ", k = " << k << ", I = " << I << ", V = " << V.GetHex());
crypto_generichash_blake2b_update(&state, (unsigned char*)&I[0], I.size());
crypto_generichash_blake2b_update(&state, V.begin(), V.size());
// First test the basic solver
std::set<std::vector<uint32_t>> ret = eh.BasicSolve(state);
std::set<std::vector<uint32_t>> ret;
EhBasicSolve(n, k, state, ret);
BOOST_TEST_MESSAGE("[Basic] Number of solutions: " << ret.size());
std::stringstream strm;
PrintSolutions(strm, ret);
@ -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<std::vector<uint32_t>> retOpt = eh.OptimisedSolve(state);
std::set<std::vector<uint32_t>> retOpt;
EhOptimisedSolve(n, k, state, retOpt);
BOOST_TEST_MESSAGE("[Optimised] Number of solutions: " << retOpt.size());
strm.str("");
PrintSolutions(strm, retOpt);
@ -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<uint32_t> soln, bool expected) {
Equihash eh {n, k};
crypto_generichash_blake2b_state state;
eh.InitialiseState(state);
EhInitialiseState(n, k, state);
uint256 V = ArithToUint256(nonce);
crypto_generichash_blake2b_update(&state, (unsigned char*)&I[0], I.size());
crypto_generichash_blake2b_update(&state, V.begin(), V.size());
@ -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) {

View File

@ -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<std::vector<unsigned int>> solns;
EhBasicSolve(n, k, eh_state, solns);
return timer_stop();
}