-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #968 from ScorexFoundation/i958-2
[6.0.0] Header.checkPow method
- Loading branch information
Showing
21 changed files
with
451 additions
and
57 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,7 @@ | |
|
||
yarn.lock | ||
*.log | ||
yarn.lock | ||
docs/spec/out/ | ||
test-out/ | ||
flamegraphs/ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package sigma.util | ||
|
||
import java.math.BigInteger | ||
|
||
object NBitsUtils { | ||
|
||
/** | ||
* <p>The "compact" format is a representation of a whole number N using an unsigned 32 bit number similar to a | ||
* floating point format. The most significant 8 bits are the unsigned exponent of base 256. This exponent can | ||
* be thought of as "number of bytes of N". The lower 23 bits are the mantissa. Bit number 24 (0x800000) represents | ||
* the sign of N. Therefore, N = (-1^sign) * mantissa * 256^(exponent-3).</p> | ||
* | ||
* <p>Satoshi's original implementation used BN_bn2mpi() and BN_mpi2bn(). MPI uses the most significant bit of the | ||
* first byte as sign. Thus 0x1234560000 is compact 0x05123456 and 0xc0de000000 is compact 0x0600c0de. Compact | ||
* 0x05c0de00 would be -0x40de000000.</p> | ||
* | ||
* <p>Bitcoin only uses this "compact" format for encoding difficulty targets, which are unsigned 256bit quantities. | ||
* Thus, all the complexities of the sign bit and using base 256 are probably an implementation accident.</p> | ||
*/ | ||
def decodeCompactBits(compact: Long): BigInt = { | ||
val size: Int = (compact >> 24).toInt & 0xFF | ||
val bytes: Array[Byte] = new Array[Byte](4 + size) | ||
bytes(3) = size.toByte | ||
if (size >= 1) bytes(4) = ((compact >> 16) & 0xFF).toByte | ||
if (size >= 2) bytes(5) = ((compact >> 8) & 0xFF).toByte | ||
if (size >= 3) bytes(6) = (compact & 0xFF).toByte | ||
decodeMPI(bytes) | ||
} | ||
|
||
/** | ||
* @see Utils#decodeCompactBits(long) | ||
*/ | ||
def encodeCompactBits(requiredDifficulty: BigInt): Long = { | ||
val value = requiredDifficulty.bigInteger | ||
var result: Long = 0L | ||
var size: Int = value.toByteArray.length | ||
if (size <= 3) { | ||
result = value.longValue << 8 * (3 - size) | ||
} else { | ||
result = value.shiftRight(8 * (size - 3)).longValue | ||
} | ||
// The 0x00800000 bit denotes the sign. | ||
// Thus, if it is already set, divide the mantissa by 256 and increase the exponent. | ||
if ((result & 0x00800000L) != 0) { | ||
result >>= 8 | ||
size += 1 | ||
} | ||
result |= size << 24 | ||
val a: Int = if (value.signum == -1) 0x00800000 else 0 | ||
result |= a | ||
result | ||
} | ||
|
||
|
||
/** Parse 4 bytes from the byte array (starting at the offset) as unsigned 32-bit integer in big endian format. */ | ||
def readUint32BE(bytes: Array[Byte]): Long = ((bytes(0) & 0xffL) << 24) | ((bytes(1) & 0xffL) << 16) | ((bytes(2) & 0xffL) << 8) | (bytes(3) & 0xffL) | ||
|
||
/** | ||
* MPI encoded numbers are produced by the OpenSSL BN_bn2mpi function. They consist of | ||
* a 4 byte big endian length field, followed by the stated number of bytes representing | ||
* the number in big endian format (with a sign bit). | ||
* | ||
*/ | ||
private def decodeMPI(mpi: Array[Byte]): BigInteger = { | ||
|
||
val length: Int = readUint32BE(mpi).toInt | ||
val buf = new Array[Byte](length) | ||
System.arraycopy(mpi, 4, buf, 0, length) | ||
|
||
if (buf.length == 0) { | ||
BigInteger.ZERO | ||
} else { | ||
val isNegative: Boolean = (buf(0) & 0x80) == 0x80 | ||
if (isNegative) buf(0) = (buf(0) & 0x7f).toByte | ||
val result: BigInteger = new BigInteger(buf) | ||
if (isNegative) { | ||
result.negate | ||
} else { | ||
result | ||
} | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
176 changes: 176 additions & 0 deletions
176
data/shared/src/main/scala/sigma/pow/Autolykos2PowValidation.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
package sigma.pow | ||
|
||
|
||
import scorex.crypto.hash.Blake2b256 | ||
import scorex.utils.{Bytes, Ints, Longs} | ||
import sigma.Header | ||
import sigma.crypto.{BcDlogGroup, BigIntegers, CryptoConstants} | ||
import sigma.util.NBitsUtils | ||
|
||
/** | ||
* Functions used to validate Autolykos2 Proof-of-Work. | ||
*/ | ||
object Autolykos2PowValidation { | ||
|
||
type Height = Int | ||
|
||
/** | ||
* k value for k-sum problem Autolykos is based on (find k numbers in table on N size) | ||
*/ | ||
private val k = 32 | ||
|
||
/** | ||
* Initial size of N value for k-sum problem Autolykos is based on (find k numbers in table on N size). | ||
* It grows from it since predefined block height in Autolykos 2. | ||
*/ | ||
private val NStart = 26 | ||
|
||
/** | ||
* Group order, used in Autolykos V.1 for non-outsourceability, | ||
* and also to obtain target in both Autolykos v1 and v2 | ||
*/ | ||
private val q: BigInt = CryptoConstants.dlogGroup.order | ||
|
||
/** | ||
* Number of elements in a table to find k-sum problem solution on top of | ||
*/ | ||
val NBase: Int = Math.pow(2, NStart.toDouble).toInt | ||
|
||
/** | ||
* Initial height since which table (`N` value) starting to increase by 5% per `IncreasePeriodForN` blocks | ||
*/ | ||
val IncreaseStart: Height = 600 * 1024 | ||
|
||
/** | ||
* Table size (`N`) increased every 50 * 1024 blocks | ||
*/ | ||
val IncreasePeriodForN: Height = 50 * 1024 | ||
|
||
/** | ||
* On this height, the table (`N` value) will stop to grow. | ||
* Max N on and after this height would be 2,143,944,600 which is still less than 2^^31. | ||
*/ | ||
val NIncreasementHeightMax: Height = 4198400 | ||
|
||
/** | ||
* Blake2b256 hash function invocation | ||
* @param in - input bit-string | ||
* @return - 256 bits (32 bytes) array | ||
*/ | ||
def hash(in: Array[Byte]): Array[Byte] = Blake2b256.hash(in) | ||
|
||
/** | ||
* Convert byte array to unsigned integer | ||
* @param in - byte array | ||
* @return - unsigned integer | ||
*/ | ||
def toBigInt(in: Array[Byte]): BigInt = BigInt(BigIntegers.fromUnsignedByteArray(in)) | ||
|
||
/** | ||
* Constant data to be added to hash function to increase its calculation time | ||
*/ | ||
val M: Array[Byte] = (0 until 1024).toArray.flatMap(i => Longs.toByteArray(i.toLong)) | ||
|
||
/** | ||
* Calculates table size (N value) for a given height (moment of time) | ||
* | ||
* @see papers/yellow/pow/ErgoPow.tex for full description and test vectors | ||
* @param headerHeight - height of a header to mine | ||
* @return - N value | ||
*/ | ||
def calcN(headerHeight: Height): Int = { | ||
val height = Math.min(NIncreasementHeightMax, headerHeight) | ||
if (height < IncreaseStart) { | ||
NBase | ||
} else { | ||
val itersNumber = (height - IncreaseStart) / IncreasePeriodForN + 1 | ||
(1 to itersNumber).foldLeft(NBase) { case (step, _) => | ||
step / 100 * 105 | ||
} | ||
} | ||
} | ||
|
||
def calcN(header: Header): Int = calcN(header.height) | ||
|
||
/** | ||
* Hash function that takes `m` and `nonceBytes` and returns a list of size `k` with numbers in | ||
* [0,`N`) | ||
*/ | ||
private def genIndexes(k: Int, seed: Array[Byte], N: Int): Seq[Int] = { | ||
val hash = Blake2b256(seed) | ||
val extendedHash = Bytes.concat(hash, hash.take(3)) | ||
(0 until k).map { i => | ||
BigInt(1, extendedHash.slice(i, i + 4)).mod(N).toInt | ||
} | ||
}.ensuring(_.length == k) | ||
|
||
/** | ||
* Generate element of Autolykos equation. | ||
*/ | ||
private def genElementV2(indexBytes: Array[Byte], heightBytes: => Array[Byte]): BigInt = { | ||
// Autolykos v. 2: H(j|h|M) (line 5 from the Algo 2 of the spec) | ||
toBigInt(hash(Bytes.concat(indexBytes, heightBytes, M)).drop(1)) | ||
} | ||
|
||
def hitForVersion2ForMessage(k: Int, msg: Array[Byte], nonce: Array[Byte], h: Array[Byte], N: Int): BigInt = { | ||
|
||
val prei8 = BigIntegers.fromUnsignedByteArray(hash(Bytes.concat(msg, nonce)).takeRight(8)) | ||
val i = BigIntegers.asUnsignedByteArray(4, prei8.mod(BigInt(N).underlying())) | ||
val f = Blake2b256(Bytes.concat(i, h, M)).drop(1) // .drop(1) is the same as takeRight(31) | ||
val seed = Bytes.concat(f, msg, nonce) // Autolykos v1, Alg. 2, line4: | ||
|
||
val indexes = genIndexes(k, seed, N) | ||
//pk and w not used in v2 | ||
val elems = indexes.map(idx => genElementV2(Ints.toByteArray(idx), h)) | ||
val f2 = elems.sum | ||
|
||
// sum as byte array is always about 32 bytes | ||
val array: Array[Byte] = BigIntegers.asUnsignedByteArray(32, f2.underlying()) | ||
val ha = hash(array) | ||
toBigInt(ha) | ||
} | ||
|
||
/** | ||
* Header digest ("message" for default GPU miners) a miner is working on | ||
*/ | ||
def msgByHeader(h: Header): Array[Byte] = Blake2b256(h.serializeWithoutPoW.toArray) | ||
|
||
/** | ||
* Get hit for Autolykos v2 header (to test it then against PoW target) | ||
* | ||
* @param header - header to check PoW for | ||
* @return PoW hit | ||
*/ | ||
def hitForVersion2(header: Header): BigInt = { | ||
|
||
val msg = msgByHeader(header) | ||
val nonce = header.powNonce | ||
|
||
val h = Ints.toByteArray(header.height) // used in AL v.2 only | ||
|
||
val N = calcN(header) | ||
|
||
hitForVersion2ForMessage(k, msg, nonce.toArray, h, N) | ||
} | ||
|
||
/** | ||
* Get target `b` from encoded difficulty `nBits` | ||
*/ | ||
def getB(nBits: Long): BigInt = { | ||
q / NBitsUtils.decodeCompactBits(nBits) | ||
} | ||
|
||
/** | ||
* Check PoW for Autolykos v2 header | ||
* | ||
* @param header - header to check PoW for | ||
* @return whether PoW is valid or not | ||
*/ | ||
def checkPoWForVersion2(header: Header): Boolean = { | ||
val b = getB(header.nBits) | ||
// for version 2, we're calculating hit and compare it with target | ||
val hit = hitForVersion2(header) | ||
hit < b | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.