Files
xahaud/src/cpp/ripple/ProofOfWork.cpp
Vinnie Falco 2c525b03c6 Enormous cleanup of RippleAddress et. al. into ripple_data
Start cleanup into ripple_data, split out some hash_value() instances
Tidy up CBigNum into ripple_data, moving definitions to .cpp
Split and clean up base58 stuff
Remove unused files from VS2012 project
Clean up some bignum stuff and remove unused files
Partial cleanup of RFC1751
Enormous cleanup with RippleAddress and related, into ripple_data
Remove unused VS project files
Move ECIES stuff into CKey
2013-05-30 10:16:22 -07:00

414 lines
13 KiB
C++

#include "ProofOfWork.h"
#include <string>
#include <boost/test/unit_test.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/format.hpp>
#include <boost/regex.hpp>
#include <openssl/rand.h>
SETUP_LOG (ProofOfWork)
bool powResultInfo(POWResult powCode, std::string& strToken, std::string& strHuman)
{
static struct {
POWResult powCode;
const char* cpToken;
const char* cpHuman;
} powResultInfoA[] = {
{ powREUSED, "powREUSED", "Proof-of-work has already been used." },
{ powBADNONCE, "powBADNONCE", "The solution does not meet the required difficulty." },
{ powEXPIRED, "powEXPIRED", "Token is expired." },
{ powCORRUPT, "powCORRUPT", "Invalid token." },
{ powTOOEASY, "powTOOEASY", "Difficulty has increased since token was issued." },
{ powOK, "powOK", "Valid proof-of-work." },
};
int iIndex = NUMBER(powResultInfoA);
while (iIndex-- && powResultInfoA[iIndex].powCode != powCode)
;
if (iIndex >= 0)
{
strToken = powResultInfoA[iIndex].cpToken;
strHuman = powResultInfoA[iIndex].cpHuman;
}
return iIndex >= 0;
}
const uint256 ProofOfWork::sMinTarget("00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
const int ProofOfWork::sMaxIterations(1 << 23);
const int ProofOfWork::sMaxDifficulty(30);
ProofOfWork::ProofOfWork(const std::string& token)
{
std::vector<std::string> fields;
boost::split(fields, token, boost::algorithm::is_any_of("-"));
if (fields.size() != 5)
throw std::runtime_error("invalid token");
mToken = token;
mChallenge.SetHex(fields[0]);
mTarget.SetHex(fields[1]);
mIterations = lexical_cast_s<int>(fields[2]);
}
bool ProofOfWork::isValid() const
{
if ((mIterations <= sMaxIterations) && (mTarget >= sMinTarget))
return true;
WriteLog (lsWARNING, ProofOfWork) << "Invalid PoW: " << mIterations << ", " << mTarget;
return false;
}
uint64 ProofOfWork::getDifficulty(const uint256& target, int iterations)
{ // calculate the approximate number of hashes required to solve this proof of work
if ((iterations > sMaxIterations) || (target < sMinTarget))
{
WriteLog (lsINFO, ProofOfWork) << "Iterations:" << iterations;
WriteLog (lsINFO, ProofOfWork) << "MaxIterat: " << sMaxIterations;
WriteLog (lsINFO, ProofOfWork) << "Target: " << target;
WriteLog (lsINFO, ProofOfWork) << "MinTarget: " << sMinTarget;
throw std::runtime_error("invalid proof of work target/iteration");
}
// more iterations means more hashes per iteration but also a larger final hash
uint64 difficulty = iterations + (iterations / 8);
// Multiply the number of hashes needed by 256 for each leading zero byte in the difficulty
const unsigned char *ptr = target.begin();
while (*ptr == 0)
{
difficulty *= 256;
++ptr;
}
difficulty = (difficulty * 256) / (*ptr + 1);
return difficulty;
}
static uint256 getSHA512Half(const std::vector<uint256>& vec)
{
return Serializer::getSHA512Half(vec.front().begin(), vec.size() * (256 / 8));
}
uint256 ProofOfWork::solve(int maxIterations) const
{
if (!isValid())
throw std::runtime_error("invalid proof of work target/iteration");
uint256 nonce;
getRand(nonce.begin(), nonce.size());
std::vector<uint256> buf2;
buf2.resize(mIterations);
std::vector<uint256> buf1;
buf1.resize(3);
buf1[0] = mChallenge;
while (maxIterations > 0)
{
buf1[1] = nonce;
buf1[2].zero();
for (int i = (mIterations - 1); i >= 0; --i)
{
buf1[2] = getSHA512Half(buf1);
buf2[i] = buf1[2];
}
if (getSHA512Half(buf2) <= mTarget)
return nonce;
++nonce;
--maxIterations;
}
return uint256();
}
bool ProofOfWork::checkSolution(const uint256& solution) const
{
if (mIterations > sMaxIterations)
return false;
std::vector<uint256> buf1;
buf1.push_back(mChallenge);
buf1.push_back(solution);
buf1.push_back(uint256());
std::vector<uint256> buf2;
buf2.resize(mIterations);
for (int i = (mIterations - 1); i >= 0; --i)
{
buf1[2] = getSHA512Half(buf1);
buf2[i] = buf1[2];
}
return getSHA512Half(buf2) <= mTarget;
}
bool ProofOfWork::validateToken(const std::string& strToken)
{
static boost::regex reToken("[[:xdigit:]]{64}-[[:xdigit:]]{64}-[[:digit:]]+-[[:digit:]]+-[[:xdigit:]]{64}");
boost::smatch smMatch;
return boost::regex_match(strToken, smMatch, reToken);
}
ProofOfWorkGenerator::ProofOfWorkGenerator() : mValidTime(180)
{
setDifficulty(1);
getRand(mSecret.begin(), mSecret.size());
}
ProofOfWork ProofOfWorkGenerator::getProof()
{
// challenge - target - iterations - time - validator
static boost::format f("%s-%s-%d-%d");
int now = static_cast<int>(time(NULL) / 4);
uint256 challenge;
getRand(challenge.begin(), challenge.size());
boost::mutex::scoped_lock sl(mLock);
std::string s = boost::str(boost::format(f) % challenge.GetHex() % mTarget.GetHex() % mIterations % now);
std::string c = mSecret.GetHex() + s;
s += "-" + Serializer::getSHA512Half(c).GetHex();
return ProofOfWork(s, mIterations, challenge, mTarget);
}
POWResult ProofOfWorkGenerator::checkProof(const std::string& token, const uint256& solution)
{ // challenge - target - iterations - time - validator
std::vector<std::string> fields;
boost::split(fields, token, boost::algorithm::is_any_of("-"));
if (fields.size() != 5)
{
WriteLog (lsDEBUG, ProofOfWork) << "PoW " << token << " is corrupt";
return powCORRUPT;
}
std::string v = mSecret.GetHex() + fields[0] + "-" + fields[1] + "-" + fields[2] + "-" + fields[3];
if (fields[4] != Serializer::getSHA512Half(v).GetHex())
{
WriteLog (lsDEBUG, ProofOfWork) << "PoW " << token << " has a bad token";
return powCORRUPT;
}
uint256 challenge, target;
challenge.SetHex(fields[0]);
target.SetHex(fields[1]);
time_t t = lexical_cast_s<time_t>(fields[3]);
time_t now = time(NULL);
int iterations = lexical_cast_s<int>(fields[2]);
{
boost::mutex::scoped_lock sl(mLock);
if ((t * 4) > (now + mValidTime))
{
WriteLog (lsDEBUG, ProofOfWork) << "PoW " << token << " has expired";
return powEXPIRED;
}
if (((iterations != mIterations) || (target != mTarget)) && getPowEntry(target, iterations) < (mPowEntry - 2))
{ // difficulty has increased more than two times since PoW requested
WriteLog (lsINFO, ProofOfWork) << "Difficulty has increased since PoW requested";
return powTOOEASY;
}
}
ProofOfWork pow(token, iterations, challenge, target);
if (!pow.checkSolution(solution))
{
WriteLog (lsDEBUG, ProofOfWork) << "PoW " << token << " has a bad nonce";
return powBADNONCE;
}
{
boost::mutex::scoped_lock sl(mLock);
if (!mSolvedChallenges.insert(powMap_vt(now, challenge)).second)
{
WriteLog (lsDEBUG, ProofOfWork) << "PoW " << token << " has been reused";
return powREUSED;
}
}
return powOK;
}
void ProofOfWorkGenerator::sweep()
{
time_t expire = time(NULL) - mValidTime;
boost::mutex::scoped_lock sl(mLock);
do
{
powMap_t::left_map::iterator it = mSolvedChallenges.left.begin();
if (it == mSolvedChallenges.left.end())
return;
if (it->first >= expire)
return;
mSolvedChallenges.left.erase(it);
} while(1);
}
void ProofOfWorkGenerator::loadHigh()
{
time_t now = time(NULL);
boost::mutex::scoped_lock sl(mLock);
if (mLastDifficultyChange == now)
return;
if (mPowEntry == 30)
return;
++mPowEntry;
mLastDifficultyChange = now;
}
void ProofOfWorkGenerator::loadLow()
{
time_t now = time(NULL);
boost::mutex::scoped_lock sl(mLock);
if (mLastDifficultyChange == now)
return;
if (mPowEntry == 0)
return;
--mPowEntry;
mLastDifficultyChange = now;
}
struct PowEntry
{
const char *target;
int iterations;
};
PowEntry PowEntries[ProofOfWork::sMaxDifficulty + 1] =
{ // target iterations hashes memory
{ "0CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 65536 }, // 1451874, 2 MB
{ "0CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 98304 }, // 2177811, 3 MB
{ "07FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 98304 }, // 3538944, 3 MB
{ "0CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 196608}, // 4355623, 6 MB
{ "07FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 131072}, // 4718592, 4 MB
{ "0CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 262144}, // 5807497, 8 MB
{ "07FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 196608}, // 7077888, 6 MB
{ "07FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 262144}, // 9437184, 8 MB
{ "07FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 393216}, // 14155776, 12MB
{ "03FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 393216}, // 28311552, 12MB
{ "00CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 262144}, // 92919965, 8 MB
{ "00CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 393216}, // 139379948, 12MB
{ "007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 262144}, // 150994944, 8 MB
{ "007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 393216}, // 226492416, 12MB
{ "000CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 49152 }, // 278759896, 1.5MB
{ "003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 262144}, // 301989888, 8 MB
{ "003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 393216}, // 452984832, 12MB
{ "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 98304 }, // 905969664, 3 MB
{ "000CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 196608}, // 1115039586, 6 MB
{ "000CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 262144}, // 1486719448 8 MB
{ "000CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 393216}, // 2230079172 12MB
{ "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 262144}, // 2415919104, 8 MB
{ "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 393216}, // 3623878656, 12MB
{ "0003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 393216}, // 7247757312, 12MB
{ "0000CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 262144}, // 23787511177, 8 MB
{ "0000CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 393216}, // 35681266766, 12MB
{ "00003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 131072}, // 38654705664, 4 MB
{ "00007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 262144}, // 38654705664, 8 MB
{ "00003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 196608}, // 57982058496, 6 MB
{ "00007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 393216}, // 57982058496, 12MB
{ "00003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 262144}, // 77309411328, 8 MB
};
int ProofOfWorkGenerator::getPowEntry(const uint256& target, int iterations)
{
for (int i = 0; i < 31; ++i)
if (PowEntries[i].iterations == iterations)
{
uint256 t;
t.SetHex(PowEntries[i].target);
if (t == target)
return i;
}
return -1;
}
void ProofOfWorkGenerator::setDifficulty(int i)
{
assert((i >= 0) && (i <= ProofOfWork::sMaxDifficulty));
time_t now = time(NULL);
boost::mutex::scoped_lock sl(mLock);
mPowEntry = i;
mIterations = PowEntries[i].iterations;
mTarget.SetHex(PowEntries[i].target);
mLastDifficultyChange = now;
}
BOOST_AUTO_TEST_SUITE(ProofOfWork_suite)
BOOST_AUTO_TEST_CASE( ProofOfWork_test )
{
ProofOfWorkGenerator gen;
ProofOfWork pow = gen.getProof();
WriteLog (lsINFO, ProofOfWork) << "Estimated difficulty: " << pow.getDifficulty();
uint256 solution = pow.solve(16777216);
if (solution.isZero())
BOOST_FAIL("Unable to solve proof of work");
if (!pow.checkSolution(solution))
BOOST_FAIL("Solution did not check");
WriteLog (lsDEBUG, ProofOfWork) << "A bad nonce error is expected";
POWResult r = gen.checkProof(pow.getToken(), uint256());
if (r != powBADNONCE)
{
Log(lsFATAL) << "POWResult = " << static_cast<int>(r);
BOOST_FAIL("Empty solution didn't show bad nonce");
}
if (gen.checkProof(pow.getToken(), solution) != powOK)
BOOST_FAIL("Solution did not check with issuer");
WriteLog (lsDEBUG, ProofOfWork) << "A reused nonce error is expected";
if (gen.checkProof(pow.getToken(), solution) != powREUSED)
BOOST_FAIL("Reuse solution not detected");
#ifdef SOLVE_POWS
for (int i = 0; i < 12; ++i)
{
gen.setDifficulty(i);
ProofOfWork pow = gen.getProof();
WriteLog (lsINFO, ProofOfWork) << "Level: " << i << ", Estimated difficulty: " << pow.getDifficulty();
uint256 solution = pow.solve(131072);
if (solution.isZero())
WriteLog (lsINFO, ProofOfWork) << "Giving up";
else
{
WriteLog (lsINFO, ProofOfWork) << "Solution found";
if (gen.checkProof(pow.getToken(), solution) != powOK)
{
WriteLog (lsFATAL, ProofOfWork) << "Solution fails";
}
}
}
#endif
}
BOOST_AUTO_TEST_SUITE_END()
// vim:ts=4