Skip to content

Commit

Permalink
Merge pull request #2148 from ergoplatform/autolykos-refactoring
Browse files Browse the repository at this point in the history
Autolykos-related code refactoring
  • Loading branch information
kushti authored Apr 26, 2024
2 parents eff29c6 + 440126e commit 698fb94
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ import scala.util.Try
* Based on k-sum problem, so general idea is to find k numbers in a table of size N, such that
* sum of numbers (or a hash of the sum) is less than target value.
*
* There are two version of Autolykos PoW scheme, Autolykos v1 and v2. The main difference is that
* Autolykos v1 is (weakly) non-outsourceable, while v2 is outsourceable and also eliminates some vectors of
* optimizations a miner could follow.
*
* See https://docs.ergoplatform.com/ErgoPow.pdf for details
*
* CPU Mining process is implemented in inefficient way and should not be used in real environment.
Expand Down Expand Up @@ -58,7 +62,8 @@ class AutolykosPowScheme(val k: Int, val n: Int) extends ScorexLogging {
val IncreasePeriodForN: Height = 50 * 1024

/**
* On this height, the table (`N` value) will stop to grow
* 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

Expand Down Expand Up @@ -97,25 +102,37 @@ class AutolykosPowScheme(val k: Int, val n: Int) extends ScorexLogging {
* Checks that `header` contains correct solution of the Autolykos PoW puzzle.
*/
def validate(header: Header): Try[Unit] = Try {
val b = getB(header.nBits)
if (header.version == 1) {
// for version 1, we check equality of left and right sides of the equation
require(checkPoWForVersion1(header, b), "Incorrect points")
require(checkPoWForVersion1(header), "Incorrect points")
} else {
// for version 2, we're calculating hit and compare it with target
val hit = hitForVersion2(header)
require(hit < b, "h(f) < b condition not met")
require(checkPoWForVersion2(header), "h(f) < b condition not met")
}
}

/**
* 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
}

/**
* Check PoW for Autolykos v1 header
*
* @param header - header to check PoW for
* @param b - PoW target
* @return whether PoW is valid or not
*/
def checkPoWForVersion1(header: Header, b: BigInt): Boolean = {
def checkPoWForVersion1(header: Header): Boolean = {

val b = getB(header.nBits) // PoW target

val version = 1: Byte

val msg = msgByHeader(header)
Expand Down Expand Up @@ -152,7 +169,6 @@ class AutolykosPowScheme(val k: Int, val n: Int) extends ScorexLogging {
* @return PoW hit
*/
def hitForVersion2(header: Header): BigInt = {
val version = 2: Byte

val msg = msgByHeader(header)
val nonce = header.powSolution.n
Expand All @@ -161,6 +177,25 @@ class AutolykosPowScheme(val k: Int, val n: Int) extends ScorexLogging {

val N = calcN(header)

hitForVersion2ForMessage(msg, nonce, h, N)
}

/**
* Get a PoW hit for custom message (not necessarily a block header) with Autolykos v2.
*
* PoW then can can be checked as hit < b, where b is PoW target value
*
* @param msg - message to check PoW on
* @param nonce - PoW nonce
* @param h - for Ergo blockchain, this is height encoded as bytes. For other use-cases, could be
* unique value on each call or constant (in the latter case more pre-computations
* could be possible
* @param N - table size
* @return pow hit
*/
def hitForVersion2ForMessage(msg: Array[Byte], nonce: Array[Byte], h: Array[Byte], N: Int): BigInt = {
val version = 2: Byte // autolykos protocol version, used in genElement only

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)
Expand Down Expand Up @@ -244,7 +279,9 @@ class AutolykosPowScheme(val k: Int, val n: Int) extends ScorexLogging {
//Proving-related code which is not critical for consensus below

/**
* Find a nonce from `minNonce` to `maxNonce`, such that header with the specified fields will contain
* Autolykos solver suitable for CPU-mining in testnet and devnets.
*
* Finds a nonce from `minNonce` to `maxNonce`, such that header with the specified fields will contain
* correct solution of the Autolykos PoW puzzle.
*/
def prove(parentOpt: Option[Header],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import scala.annotation.tailrec
* in range from 0 to a maximum number divisible by q without remainder.
* If yes, it returns the result mod q, otherwise make one more iteration using hash as an input.
* This is done to ensure uniform distribution of the resulting numbers.
*
* Used in Autolykos v1 only!
*/
class NumericHash(val q: BigInt) extends ScorexLogging with ScorexEncoding {
class ModQHash(val q: BigInt) extends ScorexLogging with ScorexEncoding {
assert(q.bigInteger.bitLength() <= 256, "We use 256 bit hash here")
// biggest number <= 2^256 that is divisible by q without remainder
val validRange: BigInt = (BigInt(2).pow(256) / q) * q
Expand Down
4 changes: 2 additions & 2 deletions ergo-core/src/main/scala/org/ergoplatform/mining/mining.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ package object mining {
// and also to obtain target in both Autolykos v1 and v2
val q: BigInt = group.order

private val hashFn: NumericHash = new NumericHash(q)
private val modQHashFn: ModQHash = new ModQHash(q)

/**
* Hash function which output is in Zq. Used in Autolykos v.1
* @param in - input (bit-string)
* @return - output(in Zq)
*/
def hashModQ(in: Array[Byte]): BigInt = hashFn.hash(in)
def hashModQ(in: Array[Byte]): BigInt = modQHashFn.hash(in)

/**
* Convert byte array to unsigned integer
Expand Down

0 comments on commit 698fb94

Please sign in to comment.