package org.bitseal.pow;
import java.math.BigInteger;
import java.text.NumberFormat;
import org.bitseal.crypt.SHA512;
import org.bitseal.util.ByteUtils;
import org.bitseal.util.TimeUtils;
import android.util.Log;
/**
* Offers various methods relating to Proof of Work calculations.<br><br>
*
* Updated for version 3 of the Bitmessage protocol.<br><br>
* See: https://bitmessage.org/wiki/Protocol_specification_v3
*
* @author Jonathan Coe
*/
public class POWProcessor
{
private static final String TAG = "POW_PROCESSOR";
/** In Bitmessage protocol version 3, the network standard value for nonce trials per byte is 1000. */
public static final long NETWORK_NONCE_TRIALS_PER_BYTE = 1000;
/** In Bitmessage protocol version 3, the network standard value for extra bytes is 1000. */
public static final long NETWORK_EXTRA_BYTES = 1000;
/** The minimum 'time to live' value to use when checking if a given payload's POW is sufficient */
private static final int MINIMUM_TIME_TO_LIVE_VALUE = 300;
// /**
// * For testing, use this version of the doPOW method to avoid waiting for POW
// * to be calculated.
// *
// * @param payload - A byte[] containing the payload to do the POW for.
// * @param expirationTime - The expiration time for this payload
// * @param nonceTrialsPerByte - The nonceTrialsPerByte value to use
// * @param extraBytes - The extraBytes value to use
// *
// * @return A random long that can act as a placeholder for a POW nonce
// */
// public long doPOW(byte[] payload, long expirationTime, long nonceTrialsPerByte, long extraBytes)
// {
// byte[] fakePOWNonce = new byte[8];
// new SecureRandom().nextBytes(fakePOWNonce);
// return ByteUtils.bytesToLong(fakePOWNonce);
// }
/**
* Does the POW for the given payload.<br />
* <b>WARNING: Takes a long time!!!</b>
*
* @param payload - A byte[] containing the payload to do the POW for.
* @param expirationTime - The expiration time for this payload
* @param nonceTrialsPerByte - The nonceTrialsPerByte value to use
* @param extraBytes - The extraBytes value to use
*
* @return A long containing the calculated POW nonce.
*/
public long doPOW(byte[] payload, long expirationTime, long nonceTrialsPerByte, long extraBytes)
{
long timeToLive = calculateTimeToLiveValue(expirationTime);
POWCalculator powCalc = new POWCalculator();
long powTarget = calculatePOWTarget(payload.length, nonceTrialsPerByte, extraBytes, timeToLive);
powCalc.setTarget(powTarget);
powCalc.setInitialHash(SHA512.sha512(payload));
Log.d(TAG, "Doing POW calculations for a payload.\n" +
"Payload length : " + NumberFormat.getIntegerInstance().format(payload.length) + " bytes\n" +
"Nonce trials per byte : " + NumberFormat.getIntegerInstance().format(nonceTrialsPerByte) + "\n" +
"Extra bytes : " + NumberFormat.getIntegerInstance().format(extraBytes) + "\n" +
"Time to live : " + TimeUtils.getTimeMessage(timeToLive) + "\n" +
"Target : " + NumberFormat.getIntegerInstance().format(powTarget));
return powCalc.execute();
}
/**
* Checks whether the proof of work done for a given payload is sufficient.
*
* @param payload - A byte[] containing the payload.
* @param nonce - A long containing the POW nonce.
* @param expirationTime - The expiration time for this payload
* @param nonceTrialsPerByte - The nonceTrialsPerByte value to use
* @param extraBytes - The extraBytes value to use
*
* @return A boolean value indicating whether or not the POW is sufficient.
*/
public boolean checkPOW(byte[] payload, long nonce, long expirationTime, long nonceTrialsPerByte, long extraBytes)
{
byte[] initialHash = SHA512.sha512(payload);
byte[] dataToHash = ByteUtils.concatenateByteArrays(ByteUtils.longToBytes(nonce), initialHash);
byte[] hash = SHA512.doubleHash(dataToHash);
long timeToLive = calculateTimeToLiveValue(expirationTime);
long value = ByteUtils.bytesToLong(hash);
long target = calculatePOWTarget(payload.length, nonceTrialsPerByte, extraBytes, timeToLive);
return value >= 0 && target >= value;
}
/**
* Calculates the 'time to live' value for a given expiration time value
*
* @param expirationTime - The expiration time for this payload
*
* @return The calculated 'time to live' value'
*/
private long calculateTimeToLiveValue(long expirationTime)
{
// Calculate the 'time to live' value for this payload
long currentTime = (System.currentTimeMillis() / 1000);
long timeToLive = expirationTime - currentTime;
if (timeToLive < MINIMUM_TIME_TO_LIVE_VALUE)
{
timeToLive = MINIMUM_TIME_TO_LIVE_VALUE;
}
return timeToLive;
}
/**
* Returns the POW target for a payload of the given length.
*
* @param length - The message length.
* @param nonceTrialsPerByte - The nonceTrialsPerByte value to use
* @param extraBytes - The extraBytes value to use
* @param timeToLive - The 'time to live' value to use
*
* @return An int representing the POW target for a message with the given length.
*/
private long calculatePOWTarget(int length, long nonceTrialsPerByte, long extraBytes, long timeToLive)
{
BigInteger powTarget = BigInteger.valueOf(2);
powTarget = powTarget.pow(64);
BigInteger lengthValue = BigInteger.valueOf(length + extraBytes);
long tempTimeValue = length + extraBytes;
tempTimeValue = tempTimeValue * timeToLive;
BigInteger timeValue = BigInteger.valueOf(tempTimeValue);
BigInteger timeTarget = BigInteger.valueOf(2);
timeTarget = timeTarget.pow(16);
timeValue = timeValue.divide(timeTarget);
BigInteger divisorValue = lengthValue.add(timeValue);
divisorValue = divisorValue.multiply(BigInteger.valueOf(nonceTrialsPerByte));
powTarget = powTarget.divide(divisorValue);
// Note that we are dividing through at least 8, so that the value is
// smaller than 2^61 and fits perfectly into a long.
return powTarget.longValue();
}
}