Bring over current copy of IfDefElse spec. (#1804)

This commit is contained in:
Danno Ferrin 2019-03-02 08:44:31 -07:00 committed by Nick Savers
parent cac8a33f7f
commit 55ff244e4a
2 changed files with 776 additions and 213 deletions

View File

@ -11,55 +11,100 @@ created: 2018-05-02
## Simple Summary
The following is a proposal for an alternate proof-of-work algorithm - **“ProgPoW”** - tuned for commodity hardware in order to close the efficiency gap available to specialized ASICs.
A new Proof-of-Work algorithm to replace Ethash that utilizes almost all parts of commodity GPUs.
## Abstract
The security of proof-of-work is built on a fair, randomized lottery where miners with similar resources have a similar chance of generating the next block.
For Ethereum - a community based on widely distributed commodity hardware - specialized ASICs enable certain participants to gain a much greater chance of generating the next block, and undermine the distributed security.
ASIC-resistance is a misunderstood problem. FPGAs, GPUs and CPUs can themselves be considered ASICs. Any algorithm that executes on a commodity ASIC can have a specialized ASIC made for it; most existing algorithms provide opportunities that reduce power usage and cost. Thus, the proper question to ask when solving ASIC-resistance is “how much more efficient will a specialized ASIC be, in comparison with commodity hardware?”
EIP<NaN> presents an algorithm that is tuned for commodity GPUs where there is minimal opportunity for ASIC specialization. This prevents specialized ASICs without resorting to a game of whack-a-mole where the network changes algorithms every few months.
ProgPoW is a proof-of-work algorithm designed to close the efficency gap available to specialized ASICs. It utilizes almost all parts of commodity hardware (GPUs), and comes pre-tuned for the most common hardware utilized in the Ethereum network.
## Motivation
Until Ethereum transitions to a pure proof-of-stake model, proof-of-work will continue to be a part of the security of the network - whether its adapted into a hybrid model (as is the case of Casper FFG), or adopted by a hard fork.
Ever since the first bitcoin mining ASIC was released, many new Proof of Work algorithms have been created with the intention of being “ASIC-resistant”. The goal of “ASIC-resistance” is to resist the centralization of PoW mining power such that these coins couldnt be so easily manipulated by a few players.
Ethash allows for the creation of an ASIC that is roughly twice as efficient as a commodity GPU. Ethashs memory accesses are paired with a very small amount of fixed compute. Most of a GPUs capacity and complexity sits idle, wasting power, while waiting for DRAM accesses. A specialized ASIC can implement a much smaller (and cheaper) compute engine that burns much less power.
This document presents an overview of the algorithm and examines what it means to be “ASIC-resistant.” Next, we compare existing PoW designs by analyzing how each algorithm executes in hardware. Finally, we present the detailed implementation by walking through the code.
As miner rewards are reduced with Casper FFG, it will remain profitable to mine on a specialized ASIC long after GPUs have exited the network. This will make it easier for an entity that has access to private ASICs to stage a 51% attack on the Ethereum network.
### ProgPoW Overview
The design goal of ProgPoW is to have the algorithms requirements match what is available on commodity GPUs: If the algorithm were to be implemented on a custom ASIC there should be little opportunity for efficiency gains compared to a commodity GPU.
The main elements of the algorithm are:
* Changes keccak_f1600 (with 64-bit words) to keccak_f800 (with 32-bit words) to reduce impact on total power
* Increases mix state.
* Adds a random sequence of math in the main loop.
* Adds reads from a small, low-latency cache that supports random addresses.
* Increases the DRAM read from 128 bytes to 256 bytes.
The random sequence changes every `PROGPOW_PERIOD` (50 blocks or about 12.5 minutes). When mining source code is generated for the random sequence and compiled on the host CPU. The GPU will execute the compiled code where what math to perform and what mix state to use are already resolved.
While a custom ASIC to implement this algorithm is still possible, the efficiency gains available are minimal. The majority of a commodity GPU is required to support the above elements. The only optimizations available are:
* Remove the graphics pipeline (displays, geometry engines, texturing, etc)
* Remove floating point math
* A few ISA tweaks, like instructions that exactly match the merge() function
These would result in minimal, roughly 1.1-1.2x, efficiency gains. This is much less than the 2x for Ethash or 50x for Cryptonight.
### Rationale for PoW on Commodity Hardware
With the growth of large mining pools, the control of hashing power has been delegated to the top few pools to provide a steadier economic return for small miners. While some have made the argument that large centralized pools defeats the purpose of “ASIC resistance,” its important to note that ASIC based coins are even more centralized for several reasons.
1. No natural distribution: There isnt an economic purpose for ultra-specialized hardware outside of mining and thus no reason for most people to have it.
2. No reserve group: Thus, theres no reserve pool of hardware or reserve pool of interested parties to jump in when coin price is volatile and attractive for manipulation.
3. High barrier to entry: Initial miners are those rich enough to invest capital and ecological resources on the unknown experiment a new coin may be. Thus, initial coin distribution through mining will be very limited causing centralized economic bias.
4. Delegated centralization vs implementation centralization: While pool centralization is delegated, hardware monoculture is not: only the limiter buyers of this hardware can participate so there isnt even the possibility of divesting control on short notice.
5. No obvious decentralization of control even with decentralized mining: Once large custom ASIC makers get into the game, designing back-doored hardware is trivial. ASIC makers have no incentive to be transparent or fair in market participation.
While the goal of “ASIC resistance” is valuable, the entire concept of “ASIC resistance” is a bit of a fallacy. CPUs and GPUs are themselves ASICs. Any algorithm that can run on a commodity ASIC (a CPU or GPU) by definition can have a customized ASIC created for it with slightly less functionality. Some algorithms are intentionally made to be “ASIC friendly” - where an ASIC implementation is drastically more efficient than the same algorithm running on general purpose hardware. The protection that this offers when the coin is unknown also makes it an attractive target for a dedicate mining ASIC company as soon as it becomes useful.
Therefore, ASIC resistance is: the efficiency difference of specilized hardware versus hardware that has a wider adoption and applicability. A smaller efficiency difference between custom vs general hardware mean higher resistance and a better algorithm. This efficiency difference is the proper metric to use when comparing the quality of PoW algorithms. Efficiency could mean absolute performance, performance per watt, or performance per dollar - they are all highly correlated. If a single entity creates and controls an ASIC that is drastically more efficient, they can gain 51% of the network hashrate and possibly stage an attack.
### Review of Existing PoW Algorithms
#### SHA256
* Potential ASIC efficiency gain ~ 1000X
The SHA algorithm is a sequence of simple math operations - additions, logical ops, and rotates.
To process a single op on a CPU or GPU requires fetching and decoding an instruction, reading data from a register file, executing the instruction, and then writing the result back to a register file. This takes significant time and power.
A single op implemented in an ASIC takes a handful of transistors and wires. This means every individual op takes negligible power, area, or time. A hashing core is built by laying out the sequence of required ops.
The hashing core can execute the required sequence of ops in much less time, and using less power or area, than doing the same sequence on a CPU or GPU. A bitcoin ASIC consists of a number of identical hashing cores and some minimal off-chip communication.
#### Scrypt and NeoScrypt
* Potential ASIC efficiency gain ~ 1000X
Scrypt and NeoScrypt are similar to SHA in the arithmetic and bitwise operations used. Unfortunately, popular coins such as Litecoin only use a scratchpad size between 32kb and 128kb for their PoW mining algorithm. This scratch pad is small enough to trivially fit on an ASIC next to the math core. The implementation of the math core would be very similar to SHA, with similar efficiency gains.
#### X11 and X16R
* Potential ASIC efficiency gain ~ 1000X
X11 (and similar X##) require an ASIC that has 11 unique hashing cores pipelined in a fixed sequence. Each individual hashing core would have similar efficiency to an individual SHA core, so the overall design will have the same efficiency gains.
X16R requires the multiple hashing cores to interact through a simple sequencing state machine. Each individual core will have similar efficiency gains and the sequencing logic will take minimal power, area, or time.
The Baikal BK-X is an existing ASIC with multiple hashing cores and a programmable sequencer. It has been upgraded to enable new algorithms that sequence the hashes in different orders.
#### Equihash
* Potential ASIC efficiency gain ~ 100X
The ~150mb of state is large but possible on an ASIC. The binning, sorting, and comparing of bit strings could be implemented on an ASIC at extremely high speed.
#### Cuckoo Cycle
* Potential ASIC efficiency gain ~ 100X
The amount of state required on-chip is not clear as there are Time/Memory Tradeoff attacks. A specialized graph traversal core would have similar efficiency gains to a SHA compute core.
#### CryptoNight
* Potential ASIC efficiency gain ~ 50X
Compared to Scrypt, CryptoNight does much less compute and requires a full 2mb of scratch pad (there is no known Time/Memory Tradeoff attack). The large scratch pad will dominate the ASIC implementation and limit the number of hashing cores, limiting the absolute performance of the ASIC. An ASIC will consist almost entirely of just on-die SRAM.
#### Ethash
* Potential ASIC efficiency gain ~ 2X
Ethash requires external memory due to the large size of the DAG. However that is all that it requires - there is minimal compute that is done on the result loaded from memory. As a result a custom ASIC could remove most of the complexity, and power, of a GPU and be just a memory interface connected to a small compute engine.
## Specification
ProgPoW is based on Ethash and follows the same general structure. The algorithm has five main changes from Ethash, each tuned for commodity GPUs while minimizing the possible advantage of a specialized ASIC.
The name of the algorithm comes from the fact that the inner loop between global memory accesses is a randomly generated program based on the block number. The random program is designed to both run efficiently on commodity GPUs and also cover most of the GPU's functionality. The random program sequence prevents the creation of a fixed pipeline implementation as seen in a specialized ASIC. The access size has also been tweaked to match contemporary GPUs.
In contrast to Ethash, the changes detailed below make ProgPoW dependent on the core compute capabilities in addition to memory bandwidth and size.
**Changes keccak_f1600 (with 64-bit words) to keccak_f800 (with 32-bit words).**
*On 64-bit architectures f1600 processes twice as many bits as f800 in roughly the same time. As GPUs are natively 32-bit architectures, f1600 takes twice as long as f800. ProgPow doesnt require all the bits f1600 can consume, thus reducing the size reduces the optimization opportunity for a specialized ASIC.*
**Increases mix state.**
*A significant part of a GPUs area, power, and complexity is the large register file. A large mix state ensures that a specialized ASIC would need to implement similar state storage, limiting any advantage.*
**Adds a random sequence of math in the main loop.**
*The random math changes every 50 blocks to amortize compilation overhead. Having a random sequence of math that reads and writes random locations within the state ensures that the ASIC executing the algorithm is fully programmable. There is no possibility to create an ASIC with a fixed pipeline that is much faster or lower power.*
**Adds reads from a small, low-latency cache that supports random addresses.**
*Another significant part of a GPUs area, power, and complexity is the memory hierarchy. Adding cached reads makes use of this hierarchy and ensures that a specialized ASIC also implements a similar hierarchy, preventing power or area savings.*
**Increases the DRAM read from 128 bytes to 256 bytes.**
*The DRAM read from the DAG is the same as Ethashs, but with the size increased to `256 bytes`. This better matches the workloads seen on commodity GPUs, preventing a specialized ASIC from being able to gain performance by optimizing the memory controller for abnormally small accesses.*
The DAG file is generated according to traditional Ethash specifications.
The DAG is generated exactly as in Ethash. All the parameters (ephoch length, DAG size, etc) are unchanged. See the original [Ethash](https://github.com/ethereum/wiki/wiki/Ethash) spec for details on generating the DAG.
ProgPoW can be tuned using the following parameters. The proposed settings have been tuned for a range of existing, commodity GPUs:
@ -74,17 +119,29 @@ ProgPoW can be tuned using the following parameters. The proposed settings have
The random program changes every `PROGPOW_PERIOD` blocks (default `50`, roughly 12.5 minutes) to ensure the hardware executing the algorithm is fully programmable. If the program only changed every DAG epoch (roughly 5 days) certain miners could have time to develop hand-optimized versions of the random sequence, giving them an undue advantage.
ProgPoW uses **FNV1a** for merging data. The existing Ethash uses FNV1 for merging, but FNV1a provides better distribution properties.
Sample code is written in C++, this should be kept in mind when evaluating the code in the specification.
All numerics are computed using unsinged 32 bit integers. Any overflows are trimmed off before proceeding to the next computation. Languages that use numerics not fixed to bit lenghts (such as Python and JavaScript) or that only use signed integers (such as Java) will need to keep their languages' quirks in mind. The extensive use of 32 bit data values aligns with modern GPUs internal data architectures.
ProgPoW uses a 32-bit variant of **FNV1a** for merging data. The existing Ethash uses a similar vaiant of FNV1 for merging, but FNV1a provides better distribution properties.
Test vectors can be found [in the test vectors file](../assets/eip-1057/test-vectors.md#fnv1a).
```cpp
const uint32_t FNV_PRIME = 0x1000193;
const uint32_t FNV_OFFSET_BASIS = 0x811c9dc5;
uint32_t fnv1a(uint32_t h, uint32_t d)
{
return (h ^ d) * FNV_PRIME;
}
```
ProgPow uses [KISS99](https://en.wikipedia.org/wiki/KISS_(algorithm)) for random number generation. This is the simplest (fewest instruction) random generator that passes the TestU01 statistical test suite. A more complex random number generator like Mersenne Twister can be efficiently implemented on a specialized ASIC, providing an opportunity for efficiency gains.
Test vectors can be found [in the test vectors file](../assets/eip-1057/test-vectors.md#kiss99).
```cpp
uint32_t fnv1a(uint32_t &h, uint32_t d)
{
return h = (h ^ d) * 0x1000193;
}
typedef struct {
uint32_t z, w, jsr, jcong;
} kiss99_t;
@ -105,7 +162,9 @@ uint32_t kiss99(kiss99_t &st)
}
```
The `LANES*REGS` of mix data is initialized from the hashs seed.
The `fill_mix` function populates an array of `int32` values used by each lane in the hash calculations.
Test vectors can be found [in the test vectors file](../assets/eip-1057/test-vectors.md#fill_mix).
```cpp
void fill_mix(
@ -116,26 +175,32 @@ void fill_mix(
{
// Use FNV to expand the per-warp seed to per-lane
// Use KISS to expand the per-lane seed to fill mix
uint32_t fnv_hash = 0x811c9dc5;
kiss99_t st;
st.z = fnv1a(fnv_hash, seed);
st.w = fnv1a(fnv_hash, seed >> 32);
st.jsr = fnv1a(fnv_hash, lane_id);
st.jcong = fnv1a(fnv_hash, lane_id);
st.z = fnv1a(FNV_OFFSET_BASIS, seed);
st.w = fnv1a(st.z, seed >> 32);
st.jsr = fnv1a(st.w, lane_id);
st.jcong = fnv1a(st.jsr, lane_id);
for (int i = 0; i < PROGPOW_REGS; i++)
mix[i] = kiss99(st);
}
```
Like ethash Keccak is used to seed the sequence per-nonce and to produce the final result. The keccak-f800 variant is used as the 32-bit word size matches the native word size of modern GPUs. The implementation is a variant of SHAKE with width=800, bitrate=576, capacity=224, output=256, and no padding. The result of keccak is treated as a 256-bit big-endian number - that is result byte 0 is the MSB of the value.
Like Ethash Keccak is used to seed the sequence per-nonce and to produce the final result. The keccak-f800 variant is used as the 32-bit word size matches the native word size of modern GPUs. The implementation is a variant of SHAKE with width=800, bitrate=576, capacity=224, output=256, and no padding. The result of keccak is treated as a 256-bit big-endian number - that is result byte 0 is the MSB of the value.
As with Ethash the input and output of the keccak function are fixed and relatively small. This means only a single "absorb" and "squeeze" phase are required. For a pseudo-code imenentation of the `keccak_f800_round` function see the `Round[b](A,RC)` function in the "Pseudo-code description of the permutations" section of the [official Keccak specs](https://keccak.team/keccak_specs_summary.html).
Test vectors can be found [in the test vectors file](../assets/eip-1057/test-vectors.md#keccak_f800_progpow).
```cpp
hash32_t keccak_f800_progpow(hash32_t header, uint64_t seed, hash32_t digest)
{
uint32_t st[25];
// Initialization
for (int i = 0; i < 25; i++)
st[i] = 0;
// Absorb phase for fixed 18 words of input
for (int i = 0; i < 8; i++)
st[i] = header.uint32s[i];
st[8] = seed;
@ -143,29 +208,206 @@ hash32_t keccak_f800_progpow(hash32_t header, uint64_t seed, hash32_t digest)
for (int i = 0; i < 8; i++)
st[10+i] = digest.uint32s[i];
// keccak_f800 call for the single absorb pass
for (int r = 0; r < 22; r++)
keccak_f800_round(st, r);
// Squeeze phase for fixed 8 words of output
hash32_t ret;
for (int i=0; i<8; i++)
ret.uint32s[i] = st[i];
return ret;
}
```
The inner loop uses FNV and KISS99 to generate a random sequence from the `prog_seed`. This random sequence determines which mix state is accessed and what random math is performed.
Since the `prog_seed` changes only once per `PROGPOW_PERIOD` (50 blocks or about 12.5 minutes) it is expected that while mining `progPowLoop` will be evaluated on the CPU to generate source code for that period's sequence. The source code will be compiled on the CPU before running on the GPU.
Test vectors can be found [in the test vectors file](../assets/eip-1057/test-vectors.md#progPowInit).
```cpp
kiss99_t progPowInit(uint64_t prog_seed, int mix_seq_dst[PROGPOW_REGS], int mix_seq_src[PROGPOW_REGS])
{
kiss99_t prog_rnd;
prog_rnd.z = fnv1a(FNV_OFFSET_BASIS, prog_seed);
prog_rnd.w = fnv1a(prog_rnd.z, prog_seed >> 32);
prog_rnd.jsr = fnv1a(prog_rnd.w, prog_seed);
prog_rnd.jcong = fnv1a(prog_rnd.jsr, prog_seed >> 32);
// Create a random sequence of mix destinations for merge() and mix sources for cache reads
// guarantees every destination merged once
// guarantees no duplicate cache reads, which could be optimized away
// Uses Fisher-Yates shuffle
for (int i = 0; i < PROGPOW_REGS; i++)
{
mix_seq_dst[i] = i;
mix_seq_src[i] = i;
}
for (int i = PROGPOW_REGS - 1; i > 0; i--)
{
int j;
j = kiss99(prog_rnd) % (i + 1);
swap(mix_seq_dst[i], mix_seq_dst[j]);
j = kiss99(prog_rnd) % (i + 1);
swap(mix_seq_src[i], mix_seq_src[j]);
}
return prog_rnd;
}
```
The math operations that merges values into the mix data are ones chosen to maintain entropy.
Test vectors can be found [in the test vectors file](../assets/eip-1057/test-vectors.md#math).
```cpp
// Merge new data from b into the value in a
// Assuming A has high entropy only do ops that retain entropy
// even if B is low entropy
// (IE don't do A&B)
uint32_t merge(uint32_t a, uint32_t b, uint32_t r)
{
switch (r % 4)
{
case 0: return (a * 33) + b;
case 1: return (a ^ b) * 33;
// prevent rotate by 0 which is a NOP
case 2: return ROTL32(a, ((r >> 16) % 31) + 1) ^ b;
case 3: return ROTR32(a, ((r >> 16) % 31) + 1) ^ b;
}
}
```
The math operations chosen for the random math are ones that are easy to implement in CUDA and OpenCL, the two main programming languages for commodity GPUs. The [mul_hi](https://www.khronos.org/registry/OpenCL/sdk/1.1/docs/man/xhtml/mul_hi.html), [min](https://www.khronos.org/registry/OpenCL/sdk/2.0/docs/man/xhtml/integerMax.html), [clz](https://www.khronos.org/registry/OpenCL/sdk/1.1/docs/man/xhtml/clz.html), and [popcount](https://www.khronos.org/registry/OpenCL/sdk/2.0/docs/man/xhtml/popcount.html) functions match the corresponding OpenCL functions. ROTL32 matches the OpenCL [rotate](https://www.khronos.org/registry/OpenCL/sdk/1.0/docs/man/xhtml/rotate.html) function. ROTR32 is rotate right, which is equivalent to `rotate(i, 32-v)`.
Test vectors can be found [in the test vectors file](../assets/eip-1057/test-vectors.md#math).
```cpp
// Random math between two input values
uint32_t math(uint32_t a, uint32_t b, uint32_t r)
{
switch (r % 11)
{
case 0: return a + b;
case 1: return a * b;
case 2: return mul_hi(a, b);
case 3: return min(a, b);
case 4: return ROTL32(a, b);
case 5: return ROTR32(a, b);
case 6: return a & b;
case 7: return a | b;
case 8: return a ^ b;
case 9: return clz(a) + clz(b);
case 10: return popcount(a) + popcount(b);
}
}
```
The flow of the inner loop is:
* Lane `(loop % LANES)` is chosen as the leader for that loop iteration
* The leader's `mix[0]` value modulo the number of 256-byte DAG entries is is used to select where to read from the full DAG
* Each lane reads `DAG_LOADS` sequential words, using `(lane ^ loop) % LANES` as the starting offset within the entry.
* The random sequence of math and cache accesses is performed
* The DAG data read at the start of the loop is merged at the end of the loop
`prog_seed` and `loop` come from the outer loop, corresponding to the current program seed (which is block_number/PROGPOW_PERIOD) and the loop iteration number. `mix` is the state array, initially filled by `fill_mix`. `dag` is the bytes of the Ethash DAG grouped into 32 bit unsigned ints in litte-endian format. On little-endian architectures this is just a normal int32 pointer to the existing DAG.
`DAG_BYTES` is set to the number of bytes in the current DAG, which is generated identically to the existing Ethash algorithm.
Test vectors can be found [in the test vectors file](../assets/eip-1057/test-vectors.md#progPowLoop).
```cpp
void progPowLoop(
const uint64_t prog_seed,
const uint32_t loop,
uint32_t mix[PROGPOW_LANES][PROGPOW_REGS],
const uint32_t *dag)
{
// dag_entry holds the 256 bytes of data loaded from the DAG
uint32_t dag_entry[PROGPOW_LANES][PROGPOW_DAG_LOADS];
// On each loop iteration rotate which lane is the source of the DAG address.
// The source lane's mix[0] value is used to ensure the last loop's DAG data feeds into this loop's address.
// dag_addr_base is which 256-byte entry within the DAG will be accessed
uint32_t dag_addr_base = mix[loop%PROGPOW_LANES][0] %
(DAG_BYTES / (PROGPOW_LANES*PROGPOW_DAG_LOADS*sizeof(uint32_t)));
for (int l = 0; l < PROGPOW_LANES; l++)
{
// Lanes access DAG_LOADS sequential words from the dag entry
// Shuffle which portion of the entry each lane accesses each iteration by XORing lane and loop.
// This prevents multi-chip ASICs from each storing just a portion of the DAG
size_t dag_addr_lane = dag_addr_base * PROGPOW_LANES + (l ^ loop) % PROGPOW_LANES;
for (int i = 0; i < PROGPOW_DAG_LOADS; i++)
dag_entry[l][i] = dag[dag_addr_lane * PROGPOW_DAG_LOADS + i];
}
// Initialize the program seed and sequences
// When mining these are evaluated on the CPU and compiled away
int mix_seq_dst[PROGPOW_REGS];
int mix_seq_src[PROGPOW_REGS];
int mix_seq_dst_cnt = 0;
int mix_seq_src_cnt = 0;
kiss99_t prog_rnd = progPowInit(prog_seed, mix_seq_dst, mix_seq_src);
int max_i = max(PROGPOW_CNT_CACHE, PROGPOW_CNT_MATH);
for (int i = 0; i < max_i; i++)
{
if (i < PROGPOW_CNT_CACHE)
{
// Cached memory access
// lanes access random 32-bit locations within the first portion of the DAG
int src = mix_seq_src[(mix_seq_src_cnt++)%PROGPOW_REGS];
int dst = mix_seq_dst[(mix_seq_dst_cnt++)%PROGPOW_REGS];
int sel = kiss99(prog_rnd);
for (int l = 0; l < PROGPOW_LANES; l++)
{
uint32_t offset = mix[l][src] % (PROGPOW_CACHE_BYTES/sizeof(uint32_t));
mix[l][dst] = merge(mix[l][dst], dag[offset], sel);
}
}
if (i < PROGPOW_CNT_MATH)
{
// Random Math
// Generate 2 unique sources
int src_rnd = kiss99(prog_rnd) % (PROGPOW_REGS * (PROGPOW_REGS-1));
int src1 = src_rnd % PROGPOW_REGS; // 0 <= src1 < PROGPOW_REGS
int src2 = src_rnd / PROGPOW_REGS; // 0 <= src2 < PROGPOW_REGS - 1
if (src2 >= src1) ++src2; // src2 is now any reg other than src1
int sel1 = kiss99(prog_rnd);
int dst = mix_seq_dst[(mix_seq_dst_cnt++)%PROGPOW_REGS];
int sel2 = kiss99(prog_rnd);
for (int l = 0; l < PROGPOW_LANES; l++)
{
uint32_t data = math(mix[l][src1], mix[l][src2], sel1);
mix[l][dst] = merge(mix[l][dst], data, sel2);
}
}
}
// Consume the global load data at the very end of the loop to allow full latency hiding
// Always merge into mix[0] to feed the offset calculation
for (int i = 0; i < PROGPOW_DAG_LOADS; i++)
{
int dst = (i==0) ? 0 : mix_seq_dst[(mix_seq_dst_cnt++)%PROGPOW_REGS];
int sel = kiss99(prog_rnd);
for (int l = 0; l < PROGPOW_LANES; l++)
mix[l][dst] = merge(mix[l][dst], dag_entry[l][i], sel);
}
}
```
The flow of the overall algorithm is:
* A keccak hash of the header + nonce to create a seed
* Use the seed to generate initial mix data
* Loop multiple times, each time hashing random loads and random math into the mix data
* Hash all the mix data into a single 256-bit value
* A final keccak hash that is compared against the target
* A final keccak hash is computed
* When mining this final value is compared against a `hash32_t` target
```cpp
bool progpow_search(
hash32_t progPowHash(
const uint64_t prog_seed, // value is (block_number/PROGPOW_PERIOD)
const uint64_t nonce,
const hash32_t header,
const hash32_t target, // miner can use a uint64_t target, doesn't need the full 256 bit target
const uint32_t *dag // gigabyte DAG located in framebuffer - the first portion gets cached
)
{
@ -191,171 +433,21 @@ bool progpow_search(
uint32_t digest_lane[PROGPOW_LANES];
for (int l = 0; l < PROGPOW_LANES; l++)
{
digest_lane[l] = 0x811c9dc5
digest_lane[l] = FNV_OFFSET_BASIS
for (int i = 0; i < PROGPOW_REGS; i++)
fnv1a(digest_lane[l], mix[l][i]);
digest_lane[l] = fnv1a(digest_lane[l], mix[l][i]);
}
// Reduce all lanes to a single 256-bit digest
for (int i = 0; i < 8; i++)
digest.uint32s[i] = 0x811c9dc5;
digest.uint32s[i] = FNV_OFFSET_BASIS;
for (int l = 0; l < PROGPOW_LANES; l++)
fnv1a(digest.uint32s[l%8], digest_lane[l])
digest.uint32s[l%8] = fnv1a(digest.uint32s[l%8], digest_lane[l])
// keccak(header .. keccak(header..nonce) .. digest);
return (keccak_f800_progpow(header, seed, digest) <= target);
keccak_f800_progpow(header, seed, digest);
}
```
The inner loop uses FNV and KISS99 to generate a random sequence from the `prog_seed`. This random sequence determines which mix state is accessed and what random math is performed. Since the `prog_seed` changes relatively infrequently it is expected that `progPowLoop` will be compiled while mining instead of interpreted on the fly.
```cpp
kiss99_t progPowInit(uint64_t prog_seed, int mix_seq_dst[PROGPOW_REGS], int mix_seq_cache[PROGPOW_REGS])
{
kiss99_t prog_rnd;
uint32_t fnv_hash = 0x811c9dc5;
prog_rnd.z = fnv1a(fnv_hash, prog_seed);
prog_rnd.w = fnv1a(fnv_hash, prog_seed >> 32);
prog_rnd.jsr = fnv1a(fnv_hash, prog_seed);
prog_rnd.jcong = fnv1a(fnv_hash, prog_seed >> 32);
// Create a random sequence of mix destinations for merge() and mix sources for cache reads
// guarantees every destination merged once
// guarantees no duplicate cache reads, which could be optimized away
// Uses Fisher-Yates shuffle
for (int i = 0; i < PROGPOW_REGS; i++)
{
mix_seq_dst[i] = i;
mix_seq_cache[i] = i;
}
for (int i = PROGPOW_REGS - 1; i > 0; i--)
{
int j;
j = kiss99(prog_rnd) % (i + 1);
swap(mix_seq_dst[i], mix_seq_dst[j]);
j = kiss99(prog_rnd) % (i + 1);
swap(mix_seq_cache[i], mix_seq_cache[j]);
}
return prog_rnd;
}
```
The math operations that merge values into the mix data are ones chosen to maintain entropy.
```cpp
// Merge new data from b into the value in a
// Assuming A has high entropy only do ops that retain entropy
// even if B is low entropy
// (IE don't do A&B)
void merge(uint32_t &a, uint32_t b, uint32_t r)
{
switch (r % 4)
{
case 0: a = (a * 33) + b; break;
case 1: a = (a ^ b) * 33; break;
// prevent rotate by 0 which is a NOP
case 2: a = ROTL32(a, ((r >> 16) % 31) + 1) ^ b; break;
case 3: a = ROTR32(a, ((r >> 16) % 31) + 1) ^ b; break;
}
}
```
The math operations chosen for the random math are ones that are easy to implement in CUDA and OpenCL, the two main programming languages for commodity GPUs.
```cpp
// Random math between two input values
uint32_t math(uint32_t a, uint32_t b, uint32_t r)
{
switch (r % 11)
{
case 0: return a + b;
case 1: return a * b;
case 2: return mul_hi(a, b);
case 3: return min(a, b);
case 4: return ROTL32(a, b);
case 5: return ROTR32(a, b);
case 6: return a & b;
case 7: return a | b;
case 8: return a ^ b;
case 9: return clz(a) + clz(b);
case 10: return popcount(a) + popcount(b);
}
}
```
The main loop:
```cpp
void progPowLoop(
const uint64_t prog_seed,
const uint32_t loop,
uint32_t mix[PROGPOW_LANES][PROGPOW_REGS],
const uint32_t *dag)
{
// All lanes share a base address for the global load
// Global offset uses mix[0] to guarantee it depends on the load result
uint32_t data_g[PROGPOW_LANES][PROGPOW_DAG_LOADS];
uint32_t offset_g = mix[loop%PROGPOW_LANES][0] % (DAG_BYTES / (PROGPOW_LANES*PROGPOW_DAG_LOADS*sizeof(uint32_t)));
for (int l = 0; l < PROGPOW_LANES; l++)
{
// global load to the 256 byte DAG entry
// every lane can access every part of the entry
uint32_t offset_l = offset_g * PROGPOW_LANES + (l ^ loop) % PROGPOW_LANES;
for (int i = 0; i < PROGPOW_DAG_LOADS; i++)
data_g[l][i] = dag[offset_l * PROGPOW_DAG_LOADS + i];
}
// Initialize the program seed and sequences
// When mining these are evaluated on the CPU and compiled away
int mix_seq_dst[PROGPOW_REGS];
int mix_seq_src[PROGPOW_REGS];
int mix_seq_dst_cnt = 0;
int mix_seq_src_cnt = 0;
kiss99_t prog_rnd = progPowInit(prog_seed, mix_seq_dst, mix_seq_src);
int max_i = max(PROGPOW_CNT_CACHE, PROGPOW_CNT_MATH);
for (int i = 0; i < max_i; i++)
{
if (i < PROGPOW_CNT_CACHE)
{
// Cached memory access
// lanes access random 32-bit locations within the first portion of the DAG
int src = mix_seq_src[(mix_seq_src_cnt++)%PROGPOW_REGS];
int dst = mix_seq_dst[(mix_seq_dst_cnt++)%PROGPOW_REGS];
int sel = kiss99(prog_rnd);
for (int l = 0; l < PROGPOW_LANES; l++)
{
uint32_t offset = mix[l][src] % (PROGPOW_CACHE_BYTES/sizeof(uint32_t));
merge(mix[l][dst], dag[offset], sel);
}
}
if (i < PROGPOW_CNT_MATH)
{
// Random Math
// Generate 2 unique sources
int src_rnd = kiss99(prog_rnd) % (PROGPOW_REGS * (PROGPOW_REGS-1));
int src1 = src_rnd % PROGPOW_REGS; // 0 <= src1 < PROGPOW_REGS
int src2 = src_rnd / PROGPOW_REGS; // 0 <= src2 < PROGPOW_REGS - 1
if (src2 >= src1) ++src2; // src2 is now any reg other than src
int sel1 = kiss99(prog_rnd);
int dst = mix_seq_dst[(mix_seq_dst_cnt++)%PROGPOW_REGS];
int sel2 = kiss99(prog_rnd);
for (int l = 0; l < PROGPOW_LANES; l++)
{
uint32_t data = math(mix[l][src1], mix[l][src2], sel1);
merge(mix[l][dst], data, sel2);
}
}
}
// Consume the global load data at the very end of the loop to allow full latency hiding
// Always merge into mix[0] to feed the offset calculation
for (int i = 0; i < PROGPOW_DAG_LOADS; i++)
{
int dst = (i==0) ? 0 : mix_seq_dst[(mix_seq_dst_cnt++)%PROGPOW_REGS];
int sel = kiss99(prog_rnd);
for (int l = 0; l < PROGPOW_LANES; l++)
merge(mix[l][dst], data_g[l][i], sel);
}
}
```
## Rationale
ProgPoW utilizes almost all parts of a commodity GPU, excluding:
@ -371,10 +463,24 @@ Since the GPU is almost fully utilized, theres little opportunity for specia
This algorithm is not backwards compatible with the existing Ethash, and will require a fork for adoption. Furthermore, the network hashrate will halve since twice as much memory is loaded per hash.
## Test Cases
The algorithm run on block 30,000 produces the following digest and result:
```
header ffeeddccbbaa9988776655443322110000112233445566778899aabbccddeeff
nonce 123456789abcdef0
digest: 11f19805c58ab46610ff9c719dcf0a5f18fa2f1605798eef770c47219274767d
result: 5b7ccd472dbefdd95b895cac8ece67ff0deb5a6bd2ecc6e162383d00c3728ece
```
Additional test vectors can be found [in the test vectors file](../assets/eip-1057/test-vectors.md#progPowHash).
## Implementation
Please refer to the official code located at [ProgPOW](https://github.com/ifdefelse/ProgPOW) for the full code, implemented in the standard ethminer.
The reference ProgPoW mining implementation located at [ProgPOW](https://github.com/ifdefelse/ProgPOW) is a derivative of ethminer so retains the GPL license.
## License and Copyright
## Copyright
The ProgPoW algorithm and this specification are a new work. Copyright and related rights are waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).
The reference ProgPoW mining implementation located at [ProgPOW](https://github.com/ifdefelse/ProgPOW) is a derivative of ethminer so retains the GPL license.

View File

@ -0,0 +1,457 @@
# Test Vectors for EIP-1057 - ProgPow
Many of these vectors are dervived from [chfast/ethash](https://github.com/chfast/ethash)
## fnv1a
| `h` | `d` | _result_ |
| -----------: | -----------: | -----------: |
| `0X811C9DC5` | `0XDDD0A47B` | `0XD37EE61A` |
| `0XD37EE61A` | `0XEE304846` | `0XDEDC7AD4` |
| `0XDEDC7AD4` | `0X00000000` | `0XA9155BBC` |
## kiss99
For `z`=`362436069`, `w`=`521288629`, `jsr`=`123456789`, and `jcong`=`380116160` the result of each iterative call to `kiss99` is as follows:
| _iteration_ | _result_ |kernel
| ----------: | -----------: |
| `1` | `769445856` |
| `2` | `742012328` |
| `3` | `2121196314` |
| `4` | `2805620942` |
| `100000` | `941074834` |
## fill_mix
For `hash_seed`=`0xEE304846DDD0A47B` and `lane_id`=`0` the values stored in the `mix` array will be
> ```
> 0x10C02F0D, 0x99891C9E, 0xC59649A0, 0x43F0394D,
> 0x24D2BAE4, 0xC4E89D4C, 0x398AD25C, 0xF5C0E467,
> 0x7A3302D6, 0xE6245C6C, 0x760726D3, 0x1F322EE7,
> 0x85405811, 0xC2F1E765, 0xA0EB7045, 0xDA39E821,
> 0x79FC6A48, 0x089E401F, 0x8488779F, 0xD79E414F,
> 0x041A826B, 0x313C0D79, 0x10125A3C, 0x3F4BDFAC,
> 0xA7352F36, 0x7E70CB54, 0x3B0BB37D, 0x74A3E24A,
> 0xCC37236A, 0xA442B311, 0x955AB27A, 0x6D175B7E
> ```
For the same hash and `lane_id`=`13` the value in the `mix` array will be
> ```
> 0x4E46D05D, 0x2E77E734, 0x2C479399, 0x70712177,
> 0xA75D7FF5, 0xBEF18D17, 0x8D42252E, 0x35B4FA0E,
> 0x462C850A, 0x2DD2B5D5, 0x5F32B5EC, 0xED5D9EED,
> 0xF9E2685E, 0x1F29DC8E, 0xA78F098B, 0x86A8687B,
> 0xEA7A10E7, 0xBE732B9D, 0x4EEBCB60, 0x94DD7D97,
> 0x39A425E9, 0xC0E782BF, 0xBA7B870F, 0x4823FF60,
> 0xF97A5A1C, 0xB00BCAF4, 0x02D0F8C4, 0x28399214,
> 0xB4CCB32D, 0x83A09132, 0x27EA8279, 0x3837DDA3
> ```
## keccak_f800_progpow
Test case 1:
| | |
| -------- | ----------------------------------------------------------------------------------------------------------------- |
| header | `0xCCDDEEFF`, `0x8899AABB`, `0x44556677`, `0x00112233`,<br>`0x33221100`, `0x77665544`, `0xBBAA9988`, `0xFFEEDDCC` |
| seed | `0x123456789ABCDEF0` |
| digest | `0x00000000`, `0x00000000`, `0x00000000`, `0x00000000`,<br>`0x00000000`, `0x00000000`, `0x00000000`, `0x00000000` |
| _result_ | `0x464830EE`, `0x7BA4D0DD`, `0x969E1798`, `0xCEC50EB6`,<br>`0x7872E2EA`, `0x597E3634`, `0xE380E73D`, `0x2F89D1E6` |
Test case 2:
| | |
| -------- | ----------------------------------------------------------------------------------------------------------------- |
| header | `0xCCDDEEFF`, `0x8899AABB`, `0x44556677`, `0x00112233`,<br>`0x33221100`, `0x77665544`, `0xBBAA9988`, `0xFFEEDDCC` |
| seed | `0xEE304846DDD0A47B` |
| digest | `0x0598F111`, `0x66B48AC5`, `0x719CFF10`, `0x5F0ACF9D`,<br>`0x162FFA18`, `0xEF8E7905`, `0x21470C77`, `0x7D767492` |
| _result_ | `0x47CD7C5B`, `0xD9FDBE2D`, `0xAC5C895B`, `0xFF67CE8E`,<br>`0x6B5AEB0D`, `0xE1C6ECD2`, `0x003D3862`, `0xCE8E72C3` |
## progPowInit
For ProgPow period 600 (block 30,000) the configurations should be
src array:
> `0x1A`, `0x1E`, `0x01`, `0x13`, `0x0B`, `0x15`, `0x0F`, `0x12`,
> `0x03`, `0x11`, `0x1F`, `0x10`, `0x1C`, `0x04`, `0x16`, `0x17`,
> `0x02`, `0x0D`, `0x1D`, `0x18`, `0x0A`, `0x0C`, `0x05`, `0x14`,
> `0x07`, `0x08`, `0x0E`, `0x1B`, `0x06`, `0x19`, `0x09`, `0x00`
dst array
> `0x00`, `0x04`, `0x1B`, `0x1A`, `0x0D`, `0x0F`, `0x11`, `0x07`,
> `0x0E`, `0x08`, `0x09`, `0x0C`, `0x03`, `0x0A`, `0x01`, `0x0B`,
> `0x06`, `0x10`, `0x1C`, `0x1F`, `0x02`, `0x13`, `0x1E`, `0x16`,
> `0x1D`, `0x05`, `0x18`, `0x12`, `0x19`, `0x17`, `0x15`, `0x14`
Kiss 99 state:
`z`=`0x6535921C` `w`=`0x29345B16`, `jsr`=`0xC0DD7F78`, `jcong`=`0x1165D7EB`
## merge
| `a` | `b` | `r` | _result_ | _path exercised_ |
| ------------ | ------------ | ------------ | ------------ | ---------------- |
| `0x3B0BB37D` | `0xA0212004` | `0x9BD26AB0` | `0x3CA34321` | mul/add |
| `0x10C02F0D` | `0x870FA227` | `0xD4F45515` | `0x91C1326A` | xor/mul |
| `0x24D2BAE4` | `0x0FFB4C9B` | `0x7FDBC2F2` | `0x2EDDD94C` | rotl/xor |
| `0xDA39E821` | `0x089C4008` | `0x8B6CD8C3` | `0x8A81E396` | rotr/xor |
## math
| `a` | `b` | `r` | _result_ | _operation exercised_ |
| ------------ | ------------ | ------------ | ------------ | ----------------------- |
| `0x8626BB1F` | `0xBBDFBC4E` | `0x883E5B49` | `0x4206776D` | add |
| `0x3F4BDFAC` | `0xD79E414F` | `0x36B71236` | `0x4C5CB214` | mul |
| `0x6D175B7E` | `0xC4E89D4C` | `0x944ECABB` | `0x53E9023F` | mul_hi32 |
| `0x2EDDD94C` | `0x7E70CB54` | `0x3F472A85` | `0x2EDDD94C` | min |
| `0x61AE0E62` | `0xe0596b32` | `0x3F472A85` | `0x61AE0E62` | min again (unsigned) |
| `0x8A81E396` | `0x3F4BDFAC` | `0xCEC46E67` | `0x1E3968A8` | rotl32 |
| `0x8A81E396` | `0x7E70CB54` | `0xDBE71FF7` | `0x1E3968A8` | rotr32 |
| `0xA7352F36` | `0xA0EB7045` | `0x59E7B9D8` | `0xA0212004` | bitwise and |
| `0xC89805AF` | `0x64291E2F` | `0x1BDC84A9` | `0xECB91FAF` | bitwise or |
| `0x760726D3` | `0x79FC6A48` | `0xC675CAC5` | `0x0FFB4C9B` | bitwise xor |
| `0x75551D43` | `0x3383BA34` | `0x2863AD31` | `0x00000003` | clz (leading zeros) |
| `0xEA260841` | `0xE92C44B7` | `0xF83FFE7D` | `0x0000001B` | popcount (number of 1s) |
## progPowLoop
For the first loop iteration of block 30,000 the seed to use for `fill_mix`
would be `0xEE304846DDD0A47B`. A two dimensional `mix` array should be created
passing the rows into `fill_mix` witht he column number as the loop argument.
The state of the mix array after the call to `progPowLoop` for block 30,000,
loop 1 are as follows.
`mix[0]` -
> ```
> 0x40E09E9C, 0x967A7DF0, 0x8626BB1F, 0x12C2392F,
> 0xA21D8305, 0x44C2702E, 0x94C93945, 0x6B66B158,
> 0x0CF00FAA, 0x26F5E6B5, 0x36EC0134, 0xC89805AF,
> 0x58118540, 0x8617DC4D, 0xC759F486, 0x8A81E396,
> 0x22443D4D, 0x64291E2F, 0x1998AB7F, 0x11C0FBBB,
> 0xBEA9C139, 0x82D1E47E, 0x7ED3E850, 0x2F81531A,
> 0xBBDFBC4E, 0xF58AEE4D, 0x3CA34321, 0x357BD48A,
> 0x2F9C8B5D, 0x2319B193, 0x2856BB38, 0x2E3C33E6
> ```
`mix[1]` -
> ```
> 0x4EB8A8F9, 0xD978BF17, 0x7D5074D4, 0x7A092D5D,
> 0x8682D1BE, 0xC3D2941C, 0xF1A1A38B, 0x54BB6D34,
> 0x2F0FB257, 0xB5464B50, 0x40927B67, 0xBB92A7E1,
> 0x1305A517, 0xE06C6765, 0xA75FD647, 0x9F232D6E,
> 0x0D9213ED, 0x8884671D, 0x54352B96, 0x6772E58E,
> 0x1B8120C9, 0x179F3CFB, 0x116FFC82, 0x6D019BCE,
> 0x1C26A750, 0x89716638, 0x02BEB948, 0x2E0AD5CE,
> 0x7FA915B2, 0x93024F2F, 0x2F58032E, 0xF02E550C
> ```
`mix[2]` -
> ```
> 0x008FF9BD, 0xC41F9802, 0x2E36FDC8, 0x9FBA2A91,
> 0x0A921670, 0x231308E6, 0xEF09A56E, 0x9657A64A,
> 0xF67723FE, 0x963DCD40, 0x354CBFDB, 0x57C07B9A,
> 0x06AF5B40, 0xBA5DE5A6, 0xDA5AAE7B, 0x9F8A5E4B,
> 0x7D6AFC9A, 0xE4783F78, 0x89B24946, 0x5EE94228,
> 0xA209DAAA, 0xDCC27C64, 0x3366FBED, 0x0FEFB673,
> 0x0FC205E3, 0xB61515B2, 0x70A45E9B, 0xBB225E5D,
> 0xB8C38EA0, 0xE01DE9B4, 0x866FAA5B, 0x1A125220
> ```
`mix[3]` -
> ```
> 0xE5F9C5CC, 0x6F75CFA2, 0xE0F50924, 0xE7B4F5EF,
> 0x779B903D, 0x5F068253, 0x05FF68E5, 0x39348653,
> 0x654B89E4, 0x0559769E, 0xA3D46B93, 0xD084454D,
> 0xCFC5CF7D, 0x8C11D8E4, 0x795BDB59, 0xD9E03113,
> 0xBAE8C355, 0x12B63814, 0x4046A018, 0xA269A32E,
> 0x54A57C4B, 0x2ED1065B, 0xB69A2C76, 0x4AEF0950,
> 0x6C2D187B, 0x8252FAE7, 0x3E9C0ED2, 0x26E47B15,
> 0xFEFB48E3, 0xDA088C7F, 0xA82B0379, 0xA49C6D86
> ```
`mix[4]` -
> ```
> 0xB926334C, 0x686A29AF, 0xD9E2EF15, 0x1C8A2D39,
> 0x307ED4F4, 0x2ABB1DB6, 0xD6F95128, 0xDFCA05F8,
> 0x904D9472, 0xEC09E200, 0x7143F47F, 0xEE488438,
> 0xFCA48DA8, 0xA64C7DD4, 0xC4AE9A30, 0xEBA30BC9,
> 0xB02630BF, 0xD1DF40CC, 0x4DFE8B7B, 0x205C97B3,
> 0xE40376F8, 0x2491117E, 0x34984321, 0xA01546A7,
> 0xB254F2F9, 0xC78A7C25, 0xFFC615E2, 0x5839FC88,
> 0x2A04DF6C, 0xC02A9A8A, 0x39238EAD, 0x7139060C
> ```
`mix[5]` -
> ```
> 0xC416E54B, 0x64AD1C57, 0xBF7CBA55, 0x176F714E,
> 0xBE733426, 0x995C4132, 0x5F50F779, 0x0F76FDF3,
> 0x526F7870, 0xE56A1A8A, 0xDCEB677E, 0xD471CC19,
> 0xA9ED60E4, 0x145E807F, 0x8D652E92, 0x80E8116F,
> 0xFF1A37EB, 0x1E0C49A1, 0x59D756DA, 0x39A8E761,
> 0x2F0F646F, 0x43F41278, 0x88CC48DA, 0x8FDFF7A4,
> 0x9AEACA2E, 0x59E7808C, 0x7F72E46B, 0xCA572333,
> 0xC6029C88, 0x7736E592, 0xF1338231, 0x262B2C7F
> ```
`mix[6]` -
> ```
> 0x3C554151, 0x70999423, 0x64BB49A8, 0xF9EBE9E9,
> 0x7D9C28CF, 0x23EE7659, 0xD6504FCF, 0x1C58C2A1,
> 0x62B9C627, 0x680AE248, 0xF196A153, 0x2A3C345A,
> 0x860E6EB2, 0x266D2652, 0x3C9F2420, 0xF790A538,
> 0x710A5523, 0xBEA2603A, 0x1C1CC272, 0xF91D482A,
> 0x1CA19931, 0x7A80ED37, 0x9572513D, 0x376F1CFE,
> 0xE57C1264, 0xE47BF931, 0xC7310E05, 0x7866CC9E,
> 0xC676BBD5, 0x4C167FEB, 0x0FE03D2B, 0x46C6D26C
> ```
`mix[7]` -
> ```
> 0x3395F65A, 0x7142A5B1, 0x97780661, 0xE5EE45B8,
> 0xCD9FDC42, 0x25BF044C, 0x0350F81B, 0x55D50703,
> 0xA8CB893E, 0xEE795201, 0xC2D6E598, 0xC2AC2D7A,
> 0xD2E81716, 0xAD876790, 0x0F3339C7, 0xEEC31E01,
> 0xA293ABF6, 0x28AE317D, 0x44A7AC05, 0xBEBA1C5E,
> 0x325ED29E, 0x4344131E, 0x921CD8DD, 0x08AB9E0B,
> 0xC18E66A6, 0x87E6BCA3, 0x24CE82AE, 0xC910B4F1,
> 0x9E513EC0, 0xA1B8CB76, 0xF0455815, 0x36BC0DCF
> ```
`mix[8]` -
> ```
> 0x0117C85F, 0xE018F2C6, 0x416C897D, 0x9D288A0F,
> 0x2AA9EA93, 0x5A6D3CEA, 0xAA99B726, 0x0A42DAB7,
> 0x72F6EA4A, 0x1DB074E6, 0x2E2A606C, 0xAC5D509B,
> 0x53F13E85, 0x1D44B521, 0x24234C42, 0xAD5BAD70,
> 0xAB2DA791, 0x6479546A, 0xD27B3771, 0xBB0A09DD,
> 0x6D3C8056, 0x96572D4B, 0x52DB6535, 0x3D242BC1,
> 0xF37D7C7A, 0xA60F7111, 0x59B59667, 0xF28635B0,
> 0xC2A8F9F5, 0x7CFB9CCB, 0xDF8697AA, 0xA3260D94
> ```
`mix[9]` -
> ```
> 0xA387FC4B, 0xC757D3A0, 0xA584E879, 0xB0A1EC29,
> 0x82CB2EC3, 0x6BF33664, 0x41FECC42, 0xF60C2AC5,
> 0xEA250BE5, 0x42BE9F33, 0x9227B0B3, 0x9080A6AB,
> 0xAF193598, 0xC708BC8A, 0x020CDEDB, 0x7FA2F773,
> 0x4338E670, 0x069E0242, 0x5AD87326, 0xD7A87124,
> 0x220D5C46, 0x26D3400D, 0x4899D1EE, 0x90EAD2F6,
> 0xFA3F1F74, 0x9C5A5D58, 0xAE20567C, 0x424B690D,
> 0xC9A4057A, 0x9F2A5CD1, 0xAA33CD5F, 0x18F58C00
> ```
`mix[10]` -
> ```
> 0xEAFE893C, 0x1ABB2971, 0x29803BB3, 0x5BC2F71F,
> 0x619DAFAD, 0xD9CFEFB6, 0xB4FEFAB5, 0x5EB249EC,
> 0x1A6E2B3A, 0xFB05DD28, 0xDCB33C2E, 0x630BB8AE,
> 0x43463B39, 0x3BD2F552, 0xFB20C0A2, 0x3383BA34,
> 0x2E9C1A99, 0x60A949B2, 0x861372AB, 0xC149D929,
> 0xA77A0A93, 0xE0CEE0D9, 0x791E7E82, 0x66A8D75A,
> 0x44D1845F, 0xE534DC4A, 0x2C7DD20C, 0xEEDAB329,
> 0x3209FE2A, 0x0C0406BC, 0xD6D4BD2A, 0x5FDB13CC
> ```
`mix[11]` -
> ```
> 0x2520ABB3, 0xCD942485, 0x9A2929BC, 0x0E10F18C,
> 0xDFB1815E, 0x8BEF05A3, 0x531A8837, 0x668838E4,
> 0xBACCE200, 0x003F85C2, 0x56226F05, 0xC2233173,
> 0x2F39A0D9, 0xF4466D0D, 0x0B9E686C, 0x82C69BDA,
> 0x0C8A8CD6, 0xA93F3001, 0x36A65EC1, 0x40CCFD7A,
> 0x84484E23, 0xF0896D45, 0x06D9F760, 0x6559142C,
> 0x9FFE2E88, 0x9593DC89, 0x89C9E3B9, 0x33285F41,
> 0x16F636C8, 0xA08169C7, 0xA5E1C956, 0xC22CCF52
> ```
`mix[12]` -
> ```
> 0xDC3B8CAA, 0xC6941197, 0x9969D596, 0x46453D3E,
> 0x568EAFEA, 0x5B823345, 0xDE606E8E, 0x7523C86D,
> 0x0EDAF441, 0x00C3D848, 0xAE5BAB99, 0xD705B9EE,
> 0x54B49E3D, 0xF364A6A4, 0x42C55975, 0xFE41EED5,
> 0xAD46170F, 0xAABE4868, 0x270379F9, 0xD33D0D7C,
> 0xF39C476C, 0xA449118E, 0x71BCC1E4, 0x5E300E77,
> 0x1CACD489, 0x4D82FABD, 0x090F9F80, 0xB2DB9626,
> 0xE12A973B, 0x1B77460C, 0xD25F89F5, 0x5753612E
> ```
`mix[13]` -
> ```
> 0x042D951C, 0x38833AA7, 0xBEA9894D, 0x7AE7F381,
> 0x42DB6723, 0x1FB0294F, 0x41452A28, 0xA7A97B9C,
> 0x228AA7EA, 0x781A7420, 0x4589736D, 0xB3C19349,
> 0x685EF9E6, 0xB4987DF6, 0xC9C3B188, 0x2DCA6A03,
> 0xE89A6D3D, 0x50EF7CF5, 0xF6274868, 0x8AA22824,
> 0x980FFDE3, 0xD4A6CB4E, 0x06FF9E1A, 0xBADB6DF5,
> 0xEDE3ADF3, 0xC9CF45F6, 0xFDFA194C, 0xAF076AA8,
> 0x7B876CEA, 0xB0C89575, 0x35A72155, 0x6CFDFC06
> ```
`mix[14]` -
> ```
> 0x0E3E28C8, 0xEC329DEC, 0x06D0A1D1, 0xF95ABEF8,
> 0x168DCF28, 0xDD7714C1, 0x769C119E, 0xA5530A7D,
> 0x1EEACB59, 0x30FD21BB, 0x082A3691, 0x1C4C9BCA,
> 0x420F27DE, 0xA8FDA3AE, 0xE182142E, 0x5102F0FF,
> 0x15B82277, 0x120C3217, 0x7BE714ED, 0xA251DCD5,
> 0x6FB4F831, 0xB71D7B32, 0xD5F7A04A, 0x763E1A20,
> 0x38E68B0C, 0xBB5A4121, 0x9340BF06, 0x948B03F8,
> 0xE71BF17B, 0x1BB5F06B, 0x26F2A200, 0x5F28C415
> ```
`mix[15]` -
> ```
> 0xC818CD64, 0xBC910343, 0xB18B7776, 0x7182DEBA,
> 0x9DB319EE, 0x9AE7F32F, 0x3CA9F8B5, 0xC63F48ED,
> 0x8321533A, 0x059C96B1, 0x8DCDA60A, 0x75B6C1D1,
> 0xC3406B57, 0x3DFE9E9B, 0xC01E1FD7, 0xC4643218,
> 0x6873F0BA, 0x8ABD36B9, 0xA74D0CBD, 0x8A637118,
> 0x6916416C, 0xB6E3A8DD, 0xB68DD4FA, 0xFBD543EE,
> 0x56F05592, 0x33D6DB82, 0x58D0A7DD, 0x18630C6E,
> 0xB33749CA, 0x5D2E87F7, 0x0F3C39DB, 0x3CAE9895
> ```
## progPowHash
Block 30000:
- `prog_seed` - 600
- `nonce` - `123456789abcdef0`
- `header` - `ffeeddccbbaa9988776655443322110000112233445566778899aabbccddeeff`
- _digest_ - `11f19805c58ab46610ff9c719dcf0a5f18fa2f1605798eef770c47219274767d`
- _result_ - `5b7ccd472dbefdd95b895cac8ece67ff0deb5a6bd2ecc6e162383d00c3728ece`
Block 0:
- `prog_seed` - 0
- `nonce` - `0000000000000000`
- `header` - `0000000000000000000000000000000000000000000000000000000000000000`
- _digest_ - `faeb1be51075b03a4ff44b335067951ead07a3b078539ace76fd56fc410557a3`
- _result_ - `63155f732f2bf556967f906155b510c917e48e99685ead76ea83f4eca03ab12`
Block 49:
- `prog_seed` - 0
- `nonce` - `0000000006ff2c47`
- `header` - `63155f732f2bf556967f906155b510c917e48e99685ead76ea83f4eca03ab12b`
- _digest_ - `c789c1180f890ec555ff42042913465481e8e6bc512cb981e1c1108dc3f2227d`
- _result_ - `9e7248f20914913a73d80a70174c331b1d34f260535ac3631d770e656b5dd92`
Block 50:
- `prog_seed` - 1
- `nonce` - `00000000076e482e`
- `header` - `9e7248f20914913a73d80a70174c331b1d34f260535ac3631d770e656b5dd922`
- _digest_ - `c7340542c2a06b3a7dc7222635f7cd402abf8b528ae971ddac6bbe2b0c7cb518`
- _result_ - `de37e1824c86d35d154cf65a88de6d9286aec4f7f10c3fc9f0fa1bcc2687188`
Block 99:
- `prog_seed` - 1
- `nonce` - `000000003917afab`
- `header` - `de37e1824c86d35d154cf65a88de6d9286aec4f7f10c3fc9f0fa1bcc2687188d`
- _digest_ - `f5e60b2c5bfddd136167a30cbc3c8dbdbd15a512257dee7964e0bc6daa9f8ba7`
- _result_ - `ac7b55e801511b77e11d52e9599206101550144525b5679f2dab19386f23dcc`
Block 29,950:
- `prog_seed` - 599
- `nonce` - `005d409dbc23a62a`
- `header` - `ac7b55e801511b77e11d52e9599206101550144525b5679f2dab19386f23dcce`
- _digest_ - `07393d15805eb08ee6fc6cb3ad4ad1010533bd0ff92d6006850246829f18fd6e`
- _result_ - `e43d7e0bdc8a4a3f6e291a5ed790b9fa1a0948a2b9e33c844888690847de19f`
Block 29,999:
- `prog_seed` - 599
- `nonce` - `005db5fa4c2a3d03`
- `header` - `e43d7e0bdc8a4a3f6e291a5ed790b9fa1a0948a2b9e33c844888690847de19f5`
- _digest_ - `7551bddf977491da2f6cfc1679299544b23483e8f8ee0931c4c16a796558a0b8`
- _result_ - `d34519f72c97cae8892c277776259db3320820cb5279a299d0ef1e155e5c645`
Block 30,000:
- `prog_seed` - 600
- `nonce` - `005db8607994ff30`
- `header` - `d34519f72c97cae8892c277776259db3320820cb5279a299d0ef1e155e5c6454`
- _digest_ - `f1c2c7c32266af9635462e6ce1c98ebe4e7e3ecab7a38aaabfbf2e731e0fbff4`
- _result_ - `8b6ce5da0b06d18db7bd8492d9e5717f8b53e7e098d9fef7886d58a6e913ef6`
Block 30,049:
- `prog_seed` - 600
- `nonce` - `005e2e215a8ca2e7`
- `header` - `8b6ce5da0b06d18db7bd8492d9e5717f8b53e7e098d9fef7886d58a6e913ef64`
- _digest_ - `57fe6a9fbf920b4e91deeb66cb0efa971e08229d1a160330e08da54af0689add`
- _result_ - `c2c46173481b9ced61123d2e293b42ede5a1b323210eb2a684df0874ffe0904`
Block 30,050:
- `prog_seed` - 601
- `nonce` - `005e30899481055e`
- `header` - `c2c46173481b9ced61123d2e293b42ede5a1b323210eb2a684df0874ffe09047`
- _digest_ - `ba30c61cc5a2c74a5ecaf505965140a08f24a296d687e78720f0b48baf712f2d`
- _result_ - `ea42197eb2ba79c63cb5e655b8b1f612c5f08aae1a49ff236795a3516d87bc7`
Block 30,099:
- `prog_seed` - 601
- `nonce` - `005ea6aef136f88b`
- `header` - `ea42197eb2ba79c63cb5e655b8b1f612c5f08aae1a49ff236795a3516d87bc71`
- _digest_ - `cfd5e46048cd133d40f261fe8704e51d3f497fc14203ac6a9ef6a0841780b1cd`
- _result_ - `49e15ba4bf501ce8fe8876101c808e24c69a859be15de554bf85dbc095491bd`
Block 59,950:
- `prog_seed` - 1,199
- `nonce` - `02ebe0503bd7b1da`
- `header` - `49e15ba4bf501ce8fe8876101c808e24c69a859be15de554bf85dbc095491bd6`
- _digest_ - `21511fbaa31fb9f5fc4998a754e97b3083a866f4de86fa7500a633346f56d773`
- _result_ - `f5c50ba5c0d6210ddb16250ec3efda178de857b2b1703d8d5403bd0f848e19c`
Block 59,999:
- `prog_seed` - 1,199
- `nonce` - `02edb6275bd221e3`
- `header` - `f5c50ba5c0d6210ddb16250ec3efda178de857b2b1703d8d5403bd0f848e19cf`
- _digest_ - `653eda37d337e39d311d22be9bbd3458d3abee4e643bee4a7280a6d08106ef98`
- _result_ - `341562d10d4afb706ec2c8d5537cb0c810de02b4ebb0a0eea5ae335af6fb2e8`
Block 10,000,000:
- `prog_seed` - 200,000
- `nonce` - `005e30899481055e`
- `header` - `efda178de857b2b1703d8d5403bd0f848e19cff5c50ba5c0d6210ddb16250ec3`
- _digest_ - `b2403f56c426177856eaf0eedd707c86ae78a432b9169c3689a67058fcf2a848`
- _result_ - `206aee640c0fd21473d5cc3654d63c80442d9e2dfa676d2801d3ec1fbab38a6d`
Block 100,000,000:
- `prog_seed` - 2,000,000
- `nonce` - `02abe0589481055e`
- `header` - `49e15ba4bf501ce8fe88765403bd0f848e19cff5c50ba5c0d6210ddb16250ec3`
- _digest_ - `ac452084d6f4e6eacf4282ad58dbd4ce7ef2653fb5e6b5c877f56928c907432a`
- _result_ - `b879f84923e71b812ef5a42ece0b5b9366c31cab218f40afe65f8a2cae448a6f`