You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tuweni.apache.org by to...@apache.org on 2019/04/26 00:24:25 UTC

[incubator-tuweni] 47/48: Add ProgPoW module

This is an automated email from the ASF dual-hosted git repository.

toulmean pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-tuweni.git

commit 7706bf200f5602f590cb9e6322562d8e91a60477
Author: Antoine Toulme <to...@apache.org>
AuthorDate: Thu Apr 25 16:22:53 2019 -0700

    Add ProgPoW module
---
 progpow/build.gradle                               |  13 +
 .../java/org/apache/tuweni/ethash/EthHash.java     | 223 +++++++
 .../org/apache/tuweni/ethash/package-info.java     |   9 +
 .../org/apache/tuweni/progpow/KISS99Random.java    |  43 ++
 .../java/org/apache/tuweni/progpow/Keccakf800.java | 217 ++++++
 .../java/org/apache/tuweni/progpow/ProgPoW.java    | 266 ++++++++
 .../org/apache/tuweni/progpow/ProgPoWMath.java     |  74 ++
 .../org/apache/tuweni/progpow/package-info.java    |   9 +
 .../org/apache/tuweni/progpow/FillMixTest.java     | 109 +++
 .../java/org/apache/tuweni/progpow/Fnv1aTest.java  |  47 ++
 .../apache/tuweni/progpow/KISS99RandomTest.java    |  39 ++
 .../org/apache/tuweni/progpow/Keccakf800Test.java  |  72 ++
 .../java/org/apache/tuweni/progpow/MergeTest.java  |  60 ++
 .../org/apache/tuweni/progpow/ProgPoWMathTest.java | 109 +++
 .../org/apache/tuweni/progpow/ProgPoWTest.java     | 743 +++++++++++++++++++++
 settings.gradle                                    |   1 +
 16 files changed, 2034 insertions(+)

diff --git a/progpow/build.gradle b/progpow/build.gradle
new file mode 100644
index 0000000..cca001b
--- /dev/null
+++ b/progpow/build.gradle
@@ -0,0 +1,13 @@
+description = 'ProgPoW - Programmatic Proof of Work'
+
+dependencies {
+  compile project(':bytes')
+  compile project(':concurrent')
+  compile project(':crypto')
+
+  testCompile project(':junit')
+  testCompile 'org.bouncycastle:bcprov-jdk15on'
+  testCompile 'org.junit.jupiter:junit-jupiter-api'
+  testCompile 'org.junit.jupiter:junit-jupiter-params'
+  testRuntime 'org.junit.jupiter:junit-jupiter-engine'
+}
diff --git a/progpow/src/main/java/org/apache/tuweni/ethash/EthHash.java b/progpow/src/main/java/org/apache/tuweni/ethash/EthHash.java
new file mode 100644
index 0000000..ce65de2
--- /dev/null
+++ b/progpow/src/main/java/org/apache/tuweni/ethash/EthHash.java
@@ -0,0 +1,223 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
+ * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
+ * to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.tuweni.ethash;
+
+import org.apache.tuweni.bytes.Bytes;
+import org.apache.tuweni.bytes.Bytes32;
+import org.apache.tuweni.crypto.Hash;
+import org.apache.tuweni.units.bigints.UInt32;
+
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+/**
+ * Implementation of EthHash utilities for Ethereum mining algorithms.
+ */
+public class EthHash {
+
+  /**
+   * Bytes in word.
+   */
+  public static int WORD_BYTES = 4;
+  /**
+   * bytes in dataset at genesis
+   */
+  public static long DATASET_BYTES_INIT = (long) Math.pow(2, 30);
+  /**
+   * dataset growth per epoch
+   */
+  public static long DATASET_BYTES_GROWTH = (long) Math.pow(2, 23);
+  /**
+   * bytes in cache at genesis
+   */
+  public static long CACHE_BYTES_INIT = (long) Math.pow(2, 24);
+
+  /**
+   * cache growth per epoch
+   */
+  public static long CACHE_BYTES_GROWTH = (long) Math.pow(2, 17);
+  /**
+   * Size of the DAG relative to the cache
+   */
+  public static int CACHE_MULTIPLIER = 1024;
+  /**
+   * blocks per epoch
+   */
+  public static int EPOCH_LENGTH = 30000;
+  /**
+   * width of mix
+   */
+  public static int MIX_BYTES = 128;
+
+  /**
+   * hash length in bytes
+   */
+  public static int HASH_BYTES = 64;
+  /**
+   * Number of words in a hash
+   */
+  private static int HASH_WORDS = HASH_BYTES / WORD_BYTES;
+  /**
+   * number of parents of each dataset element
+   */
+  public static int DATASET_PARENTS = 256;
+  /**
+   * number of rounds in cache production
+   */
+  public static int CACHE_ROUNDS = 3;
+  /**
+   * number of accesses in hashimoto loop
+   */
+  public static int ACCESSES = 64;
+
+  public static int FNV_PRIME = 0x01000193;
+
+  /**
+   * Calculates the EthHash Epoch for a given block number.
+   *
+   * @param block Block Number
+   * @return EthHash Epoch
+   */
+  public static long epoch(long block) {
+    return block / EPOCH_LENGTH;
+  }
+
+  /**
+   * Provides the size of the cache at a given block number
+   * 
+   * @param block_number the block number
+   * @return the size of the cache at the block number, in bytes
+   */
+  public static int getCacheSize(long block_number) {
+    long sz = CACHE_BYTES_INIT + CACHE_BYTES_GROWTH * (block_number / EPOCH_LENGTH);
+    sz -= HASH_BYTES;
+    while (!isPrime(sz / HASH_BYTES)) {
+      sz -= 2 * HASH_BYTES;
+    }
+    return (int) sz;
+  }
+
+  /**
+   * Provides the size of the full dataset at a given block number
+   * 
+   * @param block_number the block number
+   * @return the size of the full dataset at the block number, in bytes
+   */
+  public static long getFullSize(long block_number) {
+    long sz = DATASET_BYTES_INIT + DATASET_BYTES_GROWTH * (block_number / EPOCH_LENGTH);
+    sz -= MIX_BYTES;
+    while (!isPrime(sz / MIX_BYTES)) {
+      sz -= 2 * MIX_BYTES;
+    }
+    return sz;
+  }
+
+  /**
+   * Generates the EthHash cache for given parameters.
+   *
+   * @param cacheSize Size of the cache to generate
+   * @param block Block Number to generate cache for
+   * @return EthHash Cache
+   */
+  public static UInt32[] mkCache(int cacheSize, long block) {
+    int rows = cacheSize / HASH_BYTES;
+    List<Bytes> cache = new ArrayList<>(rows);
+    cache.add(Hash.keccak512(dagSeed(block)));
+
+    for (int i = 1; i < rows; ++i) {
+      cache.add(Hash.keccak512(cache.get(i - 1)));
+    }
+
+    Bytes completeCache = Bytes.concatenate(cache.toArray(new Bytes[cache.size()]));
+
+    byte[] temp = new byte[HASH_BYTES];
+    for (int i = 0; i < CACHE_ROUNDS; ++i) {
+      for (int j = 0; j < rows; ++j) {
+        int offset = j * HASH_BYTES;
+        for (int k = 0; k < HASH_BYTES; ++k) {
+          temp[k] = (byte) (completeCache.get((j - 1 + rows) % rows * HASH_BYTES + k)
+              ^ (completeCache.get(
+                  Integer.remainderUnsigned(completeCache.getInt(offset, ByteOrder.LITTLE_ENDIAN), rows) * HASH_BYTES
+                      + k)));
+        }
+        temp = Hash.keccak512(temp);
+        System.arraycopy(temp, 0, completeCache.toArrayUnsafe(), offset, HASH_BYTES);
+      }
+    }
+    UInt32[] result = new UInt32[completeCache.size() / 4];
+    for (int i = 0; i < result.length; i++) {
+      result[i] = UInt32.fromBytes(completeCache.slice(i * 4, 4).reverse());
+    }
+
+    return result;
+  }
+
+  /**
+   * Calculate a data set item based on the previous cache for a given index
+   * 
+   * @param cache the DAG cache
+   * @param index the current index
+   * @return a new DAG item to append to the DAG
+   */
+  public static Bytes calcDatasetItem(UInt32[] cache, int index) {
+    int rows = cache.length / HASH_WORDS;
+    UInt32[] mixInts = new UInt32[HASH_BYTES / 4];
+    int offset = index % rows * HASH_WORDS;
+    mixInts[0] = cache[offset].xor(UInt32.valueOf(index));
+    System.arraycopy(cache, offset + 1, mixInts, 1, HASH_WORDS - 1);
+    Bytes buffer = intToByte(mixInts);
+    buffer = Hash.keccak512(buffer);
+    for (int i = 0; i < mixInts.length; i++) {
+      mixInts[i] = UInt32.fromBytes(buffer.slice(i * 4, 4).reverse());
+    }
+    for (int i = 0; i < DATASET_PARENTS; ++i) {
+      fnvHash(
+          mixInts,
+          cache,
+          fnv(UInt32.valueOf(index).xor(UInt32.valueOf(i)), mixInts[i % 16]).mod(UInt32.valueOf(rows)).multiply(
+              UInt32.valueOf(HASH_WORDS)));
+    }
+    return Hash.keccak512(intToByte(mixInts));
+  }
+
+  private static Bytes dagSeed(long block) {
+    Bytes32 seed = Bytes32.wrap(new byte[32]);
+    if (Long.compareUnsigned(block, EPOCH_LENGTH) >= 0) {
+      for (int i = 0; i < Long.divideUnsigned(block, EPOCH_LENGTH); i++) {
+        seed = Hash.keccak256(seed);
+      }
+    }
+    return seed;
+  }
+
+  private static UInt32 fnv(UInt32 v1, UInt32 v2) {
+    return (v1.multiply(FNV_PRIME)).xor(v2);
+  }
+
+  private static void fnvHash(UInt32[] mix, UInt32[] cache, UInt32 offset) {
+    for (int i = 0; i < mix.length; i++) {
+      mix[i] = fnv(mix[i], cache[offset.intValue() + i]);
+    }
+  }
+
+  private static Bytes intToByte(UInt32[] ints) {
+    return Bytes.concatenate(Stream.of(ints).map(i -> i.toBytes().reverse()).toArray(Bytes[]::new));
+  }
+
+  private static boolean isPrime(long number) {
+    return number > 2 && IntStream.rangeClosed(2, (int) Math.sqrt(number)).noneMatch(n -> (number % n == 0));
+  }
+}
diff --git a/progpow/src/main/java/org/apache/tuweni/ethash/package-info.java b/progpow/src/main/java/org/apache/tuweni/ethash/package-info.java
new file mode 100644
index 0000000..e754b70
--- /dev/null
+++ b/progpow/src/main/java/org/apache/tuweni/ethash/package-info.java
@@ -0,0 +1,9 @@
+/**
+ * Ethereum EthHash mining utilities.
+ *
+ * Code derived from code written by Danno Ferrin under the Pantheon client, under ASF license.
+ */
+@ParametersAreNonnullByDefault
+package org.apache.tuweni.ethash;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/progpow/src/main/java/org/apache/tuweni/progpow/KISS99Random.java b/progpow/src/main/java/org/apache/tuweni/progpow/KISS99Random.java
new file mode 100644
index 0000000..a6d173f
--- /dev/null
+++ b/progpow/src/main/java/org/apache/tuweni/progpow/KISS99Random.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
+ * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
+ * to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.tuweni.progpow;
+
+import org.apache.tuweni.units.bigints.UInt32;
+
+final class KISS99Random {
+
+  private static final UInt32 FILTER = UInt32.valueOf(65535);
+
+  UInt32 z;
+  UInt32 w;
+  UInt32 jsr;
+  UInt32 jcong;
+
+  KISS99Random(UInt32 z, UInt32 w, UInt32 jsr, UInt32 jcong) {
+    this.z = z;
+    this.w = w;
+    this.jsr = jsr;
+    this.jcong = jcong;
+  }
+
+  UInt32 generate() {
+    z = (z.and(FILTER).multiply(UInt32.valueOf(36969))).add(z.shiftRight(16));
+    w = (w.and(FILTER).multiply(UInt32.valueOf(18000))).add(w.shiftRight(16));
+    UInt32 mwc = z.shiftLeft(16).add(w);
+    jsr = jsr.xor(jsr.shiftLeft(17));
+    jsr = jsr.xor(jsr.shiftRight(13));
+    jsr = jsr.xor(jsr.shiftLeft(5));
+    jcong = (jcong.multiply(UInt32.valueOf(69069))).add(UInt32.valueOf(1234567));
+    return mwc.xor(jcong).add(jsr);
+  }
+}
diff --git a/progpow/src/main/java/org/apache/tuweni/progpow/Keccakf800.java b/progpow/src/main/java/org/apache/tuweni/progpow/Keccakf800.java
new file mode 100644
index 0000000..ab7f279
--- /dev/null
+++ b/progpow/src/main/java/org/apache/tuweni/progpow/Keccakf800.java
@@ -0,0 +1,217 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
+ * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
+ * to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.tuweni.progpow;
+
+import org.apache.tuweni.bytes.Bytes32;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+final class Keccakf800 {
+
+  private static int[] keccakRoundConstants = {
+      0x00000001,
+      0x00008082,
+      0x0000808A,
+      0x80008000,
+      0x0000808B,
+      0x80000001,
+      0x80008081,
+      0x00008009,
+      0x0000008A,
+      0x00000088,
+      0x80008009,
+      0x8000000A,
+      0x8000808B,
+      0x0000008B,
+      0x00008089,
+      0x00008003,
+      0x00008002,
+      0x00000080,
+      0x0000800A,
+      0x8000000A,
+      0x80008081,
+      0x00008080,};
+
+  /**
+   * Derived from {#link org.bouncycastle.crypto.digests.KeccakDigest#KeccakPermutation()}. Copyright (c) 2000-2017 The
+   * Legion Of The Bouncy Castle Inc. (http://www.bouncycastle.org) The original source is licensed under a MIT license.
+   */
+  static void keccakf800(int[] state) {
+    int a00 = state[0], a01 = state[1], a02 = state[2], a03 = state[3], a04 = state[4];
+    int a05 = state[5], a06 = state[6], a07 = state[7], a08 = state[8], a09 = state[9];
+    int a10 = state[10], a11 = state[11], a12 = state[12], a13 = state[13], a14 = state[14];
+    int a15 = state[15], a16 = state[16], a17 = state[17], a18 = state[18], a19 = state[19];
+    int a20 = state[20], a21 = state[21], a22 = state[22], a23 = state[23], a24 = state[24];
+
+    for (int i = 0; i < 22; i++) {
+      // theta
+      int c0 = a00 ^ a05 ^ a10 ^ a15 ^ a20;
+      int c1 = a01 ^ a06 ^ a11 ^ a16 ^ a21;
+      int c2 = a02 ^ a07 ^ a12 ^ a17 ^ a22;
+      int c3 = a03 ^ a08 ^ a13 ^ a18 ^ a23;
+      int c4 = a04 ^ a09 ^ a14 ^ a19 ^ a24;
+
+      int d1 = (c1 << 1 | c1 >>> 31) ^ c4;
+      int d2 = (c2 << 1 | c2 >>> 31) ^ c0;
+      int d3 = (c3 << 1 | c3 >>> 31) ^ c1;
+      int d4 = (c4 << 1 | c4 >>> 31) ^ c2;
+      int d0 = (c0 << 1 | c0 >>> 31) ^ c3;
+
+      a00 ^= d1;
+      a05 ^= d1;
+      a10 ^= d1;
+      a15 ^= d1;
+      a20 ^= d1;
+      a01 ^= d2;
+      a06 ^= d2;
+      a11 ^= d2;
+      a16 ^= d2;
+      a21 ^= d2;
+      a02 ^= d3;
+      a07 ^= d3;
+      a12 ^= d3;
+      a17 ^= d3;
+      a22 ^= d3;
+      a03 ^= d4;
+      a08 ^= d4;
+      a13 ^= d4;
+      a18 ^= d4;
+      a23 ^= d4;
+      a04 ^= d0;
+      a09 ^= d0;
+      a14 ^= d0;
+      a19 ^= d0;
+      a24 ^= d0;
+
+      // rho/pi
+      c1 = a01 << 1 | a01 >>> 31;
+      a01 = a06 << 12 | a06 >>> 20;
+      a06 = a09 << 20 | a09 >>> 12;
+      a09 = a22 << 29 | a22 >>> 3;
+      a22 = a14 << 7 | a14 >>> 25;
+      a14 = a20 << 18 | a20 >>> 14;
+      a20 = a02 << 30 | a02 >>> 2;
+      a02 = a12 << 11 | a12 >>> 21;
+      a12 = a13 << 25 | a13 >>> 7;
+      a13 = a19 << 8 | a19 >>> 24;
+      a19 = a23 << 24 | a23 >>> 8;
+      a23 = a15 << 9 | a15 >>> 23;
+      a15 = a04 << 27 | a04 >>> 5;
+      a04 = a24 << 14 | a24 >>> 18;
+      a24 = a21 << 2 | a21 >>> 30;
+      a21 = a08 << 23 | a08 >>> 9;
+      a08 = a16 << 13 | a16 >>> 19;
+      a16 = a05 << 4 | a05 >>> 28;
+      a05 = a03 << 28 | a03 >>> 4;
+      a03 = a18 << 21 | a18 >>> 11;
+      a18 = a17 << 15 | a17 >>> 17;
+      a17 = a11 << 10 | a11 >>> 22;
+      a11 = a07 << 6 | a07 >>> 26;
+      a07 = a10 << 3 | a10 >>> 29;
+      a10 = c1;
+
+      // chi
+      c0 = a00 ^ (~a01 & a02);
+      c1 = a01 ^ (~a02 & a03);
+      a02 ^= ~a03 & a04;
+      a03 ^= ~a04 & a00;
+      a04 ^= ~a00 & a01;
+      a00 = c0;
+      a01 = c1;
+
+      c0 = a05 ^ (~a06 & a07);
+      c1 = a06 ^ (~a07 & a08);
+      a07 ^= ~a08 & a09;
+      a08 ^= ~a09 & a05;
+      a09 ^= ~a05 & a06;
+      a05 = c0;
+      a06 = c1;
+
+      c0 = a10 ^ (~a11 & a12);
+      c1 = a11 ^ (~a12 & a13);
+      a12 ^= ~a13 & a14;
+      a13 ^= ~a14 & a10;
+      a14 ^= ~a10 & a11;
+      a10 = c0;
+      a11 = c1;
+
+      c0 = a15 ^ (~a16 & a17);
+      c1 = a16 ^ (~a17 & a18);
+      a17 ^= ~a18 & a19;
+      a18 ^= ~a19 & a15;
+      a19 ^= ~a15 & a16;
+      a15 = c0;
+      a16 = c1;
+
+      c0 = a20 ^ (~a21 & a22);
+      c1 = a21 ^ (~a22 & a23);
+      a22 ^= ~a23 & a24;
+      a23 ^= ~a24 & a20;
+      a24 ^= ~a20 & a21;
+      a20 = c0;
+      a21 = c1;
+
+      // iota
+      a00 ^= keccakRoundConstants[i];
+    }
+
+    state[0] = a00;
+    state[1] = a01;
+    state[2] = a02;
+    state[3] = a03;
+    state[4] = a04;
+    state[5] = a05;
+    state[6] = a06;
+    state[7] = a07;
+    state[8] = a08;
+    state[9] = a09;
+    state[10] = a10;
+    state[11] = a11;
+    state[12] = a12;
+    state[13] = a13;
+    state[14] = a14;
+    state[15] = a15;
+    state[16] = a16;
+    state[17] = a17;
+    state[18] = a18;
+    state[19] = a19;
+    state[20] = a20;
+    state[21] = a21;
+    state[22] = a22;
+    state[23] = a23;
+    state[24] = a24;
+  }
+
+  static Bytes32 keccakF800Progpow(Bytes32 header, long seed, Bytes32 digest) {
+    int[] state = new int[25];
+
+    for (int i = 0; i < 8; i++) {
+      state[i] = header.getInt(i * 4, ByteOrder.LITTLE_ENDIAN);
+    }
+    state[8] = (int) seed;
+    state[9] = (int) (seed >> 32);
+    for (int i = 0; i < 8; i++) {
+      state[10 + i] = digest.getInt(i * 4);
+    }
+
+    keccakf800(state);
+
+    ByteBuffer buffer = ByteBuffer.allocate(32).order(ByteOrder.LITTLE_ENDIAN);
+
+    for (int i = 0; i < 8; i++) {
+      buffer.putInt(i * 4, state[i]);
+    }
+    return Bytes32.wrap(buffer.array());
+  }
+}
diff --git a/progpow/src/main/java/org/apache/tuweni/progpow/ProgPoW.java b/progpow/src/main/java/org/apache/tuweni/progpow/ProgPoW.java
new file mode 100644
index 0000000..ff06b6f
--- /dev/null
+++ b/progpow/src/main/java/org/apache/tuweni/progpow/ProgPoW.java
@@ -0,0 +1,266 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
+ * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
+ * to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.tuweni.progpow;
+
+import org.apache.tuweni.bytes.Bytes;
+import org.apache.tuweni.bytes.Bytes32;
+import org.apache.tuweni.ethash.EthHash;
+import org.apache.tuweni.units.bigints.UInt32;
+import org.apache.tuweni.units.bigints.UInt64;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+/**
+ * Ethereum ProgPoW mining algorithm, based on revision 0.9.2.
+ *
+ * @implSpec https://github.com/ifdefelse/ProgPOW
+ */
+public final class ProgPoW {
+
+  static int PROGPOW_PERIOD = 50;
+  static int PROGPOW_LANES = 16;
+  static int PROGPOW_REGS = 32;
+  static int PROGPOW_DAG_LOADS = 4;
+  static int PROGPOW_CACHE_BYTES = 16 * 1024;
+  static int PROGPOW_CNT_DAG = 64;
+  static int PROGPOW_CNT_CACHE = 12;
+  static int PROGPOW_CNT_MATH = 20;
+  static UInt32 FNV_PRIME = UInt32.fromHexString("0x1000193");
+  static Bytes FNV_OFFSET_BASIS = Bytes.fromHexString("0x811c9dc5");
+  static int HASH_BYTES = 64;
+  static int HASH_WORDS = 16;
+  static int DATASET_PARENTS = 256;
+
+  /**
+   * Creates a hash using the ProgPoW formulation of a block
+   *
+   * @param blockNumber the block number of the block
+   * @param nonce the nonce of the block
+   * @param header the header of the block
+   * @param dag the directed acyclic graph cache
+   * @param dagLookupFunction the function to append to the DAG
+   * @return a hash matching the block input, using the ProgPoW algorithm
+   */
+  public static Bytes32 progPowHash(
+      long blockNumber,
+      long nonce,
+      Bytes32 header,
+      UInt32[] dag, // gigabyte DAG located in framebuffer - the first portion gets cached
+      Function<Integer, Bytes> dagLookupFunction) {
+    UInt32[][] mix = new UInt32[PROGPOW_LANES][PROGPOW_REGS];
+
+    // keccak(header..nonce)
+    Bytes32 seed_256 = Keccakf800.keccakF800Progpow(header, nonce, Bytes32.ZERO);
+
+    // endian swap so byte 0 of the hash is the MSB of the value
+    long seed = (Integer.toUnsignedLong(seed_256.getInt(0)) << 32) | Integer.toUnsignedLong(seed_256.getInt(4));
+    // initialize mix for all lanes
+    for (int l = 0; l < PROGPOW_LANES; l++)
+      mix[l] = fillMix(UInt64.fromBytes(Bytes.wrap(ByteBuffer.allocate(8).putLong(seed).array())), UInt32.valueOf(l));
+    // execute the randomly generated inner loop
+    for (int i = 0; i < PROGPOW_CNT_DAG; i++)
+      progPowLoop(blockNumber, UInt32.valueOf(i), mix, dag, dagLookupFunction);
+
+    // Reduce mix data to a per-lane 32-bit digest
+    UInt32[] digest_lane = new UInt32[PROGPOW_LANES];
+    for (int l = 0; l < PROGPOW_LANES; l++) {
+      digest_lane[l] = UInt32.fromBytes(FNV_OFFSET_BASIS);
+      for (int i = 0; i < PROGPOW_REGS; i++)
+        digest_lane[l] = fnv1a(digest_lane[l], mix[l][i]);
+    }
+    // Reduce all lanes to a single 256-bit digest
+    UInt32[] digest = new UInt32[8];
+    Arrays.fill(digest, UInt32.fromBytes(FNV_OFFSET_BASIS));
+
+    for (int l = 0; l < PROGPOW_LANES; l++) {
+      digest[l % 8] = fnv1a(digest[l % 8], digest_lane[l]);
+    }
+
+    Bytes32 bytesDigest = Bytes32.wrap(Bytes.concatenate(Stream.of(digest).map(UInt32::toBytes).toArray(Bytes[]::new)));
+
+    // keccak(header .. keccak(header..nonce) .. digest);
+    return Keccakf800.keccakF800Progpow(header, seed, bytesDigest);
+  }
+
+  /**
+   * Creates a cache for the DAG at a given block number
+   * 
+   * @param blockNumber the block number
+   * @param datasetLookup the function generating elements of the DAG
+   * @return a cache of the DAG up to the block number
+   */
+  public static UInt32[] createDagCache(long blockNumber, Function<Integer, Bytes> datasetLookup) {
+    // TODO size of cache should be function of blockNumber - and DAG should be stored in its own memory structure.
+    // cache the first 16KB of the dag
+    UInt32[] cdag = new UInt32[HASH_BYTES * DATASET_PARENTS];
+    for (int i = 0; i < cdag.length; i++) {
+      // this could be sped up 16x
+      Bytes lookup = datasetLookup.apply(i >> 4);
+      cdag[i] = UInt32.fromBytes(lookup.slice((i & 0xf) << 2, 4).reverse());
+    }
+    return cdag;
+  }
+
+  static KISS99Random progPowInit(UInt64 prog_seed, int[] mix_seq_src, int[] mix_seq_dst) {
+    UInt32 leftSeed = UInt32.fromBytes(prog_seed.toBytes().slice(0, 4));
+    UInt32 rightSeed = UInt32.fromBytes(prog_seed.toBytes().slice(4));
+    UInt32 z = fnv1a(UInt32.fromBytes(FNV_OFFSET_BASIS), rightSeed);
+    UInt32 w = fnv1a(z, leftSeed);
+    UInt32 jsr = fnv1a(w, rightSeed);
+    UInt32 jcong = fnv1a(jsr, leftSeed);
+    KISS99Random prog_rnd = new KISS99Random(z, w, jsr, jcong);
+
+    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 = prog_rnd.generate().mod(UInt32.valueOf(i + 1)).intValue();
+      int buffer = mix_seq_dst[i];
+      mix_seq_dst[i] = mix_seq_dst[j];
+      mix_seq_dst[j] = buffer;
+      j = prog_rnd.generate().mod(UInt32.valueOf(i + 1)).intValue();
+      buffer = mix_seq_src[i];
+      mix_seq_src[i] = mix_seq_src[j];
+      mix_seq_src[j] = buffer;
+    }
+    return prog_rnd;
+  }
+
+  static UInt32 merge(UInt32 a, UInt32 b, UInt32 r) {
+    switch (r.mod(UInt32.valueOf(4)).intValue()) {
+      case 0:
+        return (a.multiply(UInt32.valueOf(33))).add(b);
+      case 1:
+        return a.xor(b).multiply(UInt32.valueOf(33));
+      // prevent rotate by 0 which is a NOP
+      case 2:
+        return ProgPoWMath.rotl32(a, (r.shiftRight(16).mod(UInt32.valueOf(31))).add(UInt32.ONE)).xor(b);
+      case 3:
+        return ProgPoWMath.rotr32(a, (r.shiftRight(16).mod(UInt32.valueOf(31))).add(UInt32.ONE)).xor(b);
+      default:
+        throw new IllegalArgumentException(
+            "r mod 4 is larger than 4" + r.toHexString() + " " + r.mod(UInt32.valueOf(4)).intValue());
+    }
+  }
+
+  static UInt32[] fillMix(UInt64 seed, UInt32 laneId) {
+
+    UInt32 z = fnv1a(UInt32.fromBytes(FNV_OFFSET_BASIS), UInt32.fromBytes(seed.toBytes().slice(4, 4)));
+    UInt32 w = fnv1a(z, UInt32.fromBytes(seed.toBytes().slice(0, 4)));
+    UInt32 jsr = fnv1a(w, laneId);
+    UInt32 jcong = fnv1a(jsr, laneId);
+
+    KISS99Random random = new KISS99Random(z, w, jsr, jcong);
+
+    UInt32[] mix = new UInt32[ProgPoW.PROGPOW_REGS];
+    for (int i = 0; i < mix.length; i++) {
+      mix[i] = random.generate();
+    }
+
+    return mix;
+  }
+
+  static UInt32 fnv1a(UInt32 h, UInt32 d) {
+    return (h.xor(d)).multiply(FNV_PRIME);
+  }
+
+  static void progPowLoop(
+      long blockNumber,
+      UInt32 loop,
+      UInt32[][] mix,
+      UInt32[] dag,
+      Function<Integer, Bytes> dagLookupFunction) {
+
+    long dagBytes = EthHash.getFullSize(blockNumber);
+
+    // dag_entry holds the 256 bytes of data loaded from the DAG
+    UInt32[][] dag_entry = new UInt32[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
+    int dag_addr_base = mix[loop.intValue() % PROGPOW_LANES][0]
+        .mod(UInt32.valueOf(java.lang.Math.toIntExact(dagBytes / (PROGPOW_LANES * PROGPOW_DAG_LOADS * Integer.BYTES))))
+        .intValue();
+    //mix[loop.intValue()%PROGPOW_LANES][0].mod(UInt32.valueOf((int) dagBytes / (PROGPOW_LANES*PROGPOW_DAG_LOADS*4))).intValue();
+    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
+      //
+      int dag_addr_lane = dag_addr_base * PROGPOW_LANES + Integer.remainderUnsigned(l ^ loop.intValue(), PROGPOW_LANES);
+      int offset = Integer.remainderUnsigned(l ^ loop.intValue(), PROGPOW_LANES);
+      for (int i = 0; i < PROGPOW_DAG_LOADS; i++) {
+        UInt32 lookup = (UInt32.valueOf(dag_addr_lane).divide(UInt32.valueOf(4))).add(offset >> 4);
+        Bytes lookupHolder = dagLookupFunction.apply(lookup.intValue());
+        int lookupOffset = (i * 4 + ((offset & 0xf) << 4)) % 64;
+        dag_entry[l][i] = UInt32.fromBytes(lookupHolder.slice(lookupOffset, 4).reverse());
+
+      }
+
+    }
+
+    // Initialize the program seed and sequences
+    // When mining these are evaluated on the CPU and compiled away
+    int[] mix_seq_dst = new int[PROGPOW_REGS];
+    int[] mix_seq_src = new int[PROGPOW_REGS];
+    int mix_seq_dst_cnt = 0;
+    int mix_seq_src_cnt = 0;
+    KISS99Random prog_rnd = progPowInit(UInt64.valueOf(blockNumber / PROGPOW_PERIOD), mix_seq_src, mix_seq_dst);
+
+    int max_i = Integer.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];
+        UInt32 sel = prog_rnd.generate();
+        for (int l = 0; l < PROGPOW_LANES; l++) {
+          UInt32 offset = mix[l][src].mod(UInt32.valueOf(PROGPOW_CACHE_BYTES / 4));
+          mix[l][dst] = merge(mix[l][dst], dag[offset.intValue()], sel);
+        }
+      }
+
+      if (i < PROGPOW_CNT_MATH) {
+        // Random ProgPoWMath
+        // Generate 2 unique sources
+        UInt32 src_rnd = prog_rnd.generate().mod(UInt32.valueOf(PROGPOW_REGS * (PROGPOW_REGS - 1)));
+        int src1 = src_rnd.mod(UInt32.valueOf(PROGPOW_REGS)).intValue(); // 0 <= src1 < PROGPOW_REGS
+        int src2 = src_rnd.divide(UInt32.valueOf(PROGPOW_REGS)).intValue(); // 0 <= src2 < PROGPOW_REGS - 1
+        if (src2 >= src1)
+          ++src2; // src2 is now any reg other than src1
+        UInt32 sel1 = prog_rnd.generate();
+        int dst = mix_seq_dst[(mix_seq_dst_cnt++) % PROGPOW_REGS];
+        UInt32 sel2 = prog_rnd.generate();
+        for (int l = 0; l < PROGPOW_LANES; l++) {
+          UInt32 data = ProgPoWMath.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];
+      UInt32 sel = prog_rnd.generate();
+      for (int l = 0; l < PROGPOW_LANES; l++) {
+        mix[l][dst] = merge(mix[l][dst], dag_entry[l][i], sel);
+      }
+    }
+  }
+}
diff --git a/progpow/src/main/java/org/apache/tuweni/progpow/ProgPoWMath.java b/progpow/src/main/java/org/apache/tuweni/progpow/ProgPoWMath.java
new file mode 100644
index 0000000..8fa6284
--- /dev/null
+++ b/progpow/src/main/java/org/apache/tuweni/progpow/ProgPoWMath.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
+ * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
+ * to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.tuweni.progpow;
+
+import static org.apache.tuweni.units.bigints.UInt32s.min;
+
+import org.apache.tuweni.units.bigints.UInt32;
+import org.apache.tuweni.units.bigints.UInt64;
+
+final class ProgPoWMath {
+
+  static UInt32 math(UInt32 a, UInt32 b, UInt32 r) {
+    switch (r.mod(UInt32.valueOf(11)).intValue()) {
+      case 0:
+        return a.add(b);
+      case 1:
+        return a.multiply(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.and(b);
+      case 7:
+        return a.or(b);
+      case 8:
+        return a.xor(b);
+      case 9:
+        return clz(a).add(clz(b));
+      case 10:
+        return popcount(a).add(popcount(b));
+      default:
+        throw new IllegalArgumentException(
+            "Value " + r + " has mod larger than 11 " + r.mod(UInt32.valueOf(11).intValue()));
+    }
+  }
+
+  private static UInt32 mul_hi(UInt32 x, UInt32 y) {
+    return UInt32
+        .fromBytes(UInt64.fromBytes(x.toBytes()).multiply(UInt64.fromBytes(y.toBytes())).toBytes().slice(0, 4));
+  }
+
+  private static UInt32 clz(UInt32 value) {
+    return UInt32.valueOf(value.numberOfLeadingZeros());
+  }
+
+  private static UInt32 popcount(UInt32 value) {
+    return UInt32.valueOf(Integer.bitCount(value.intValue()));
+  }
+
+  static UInt32 rotl32(UInt32 var, UInt32 hops) {
+    return var.shiftLeft(hops.mod(UInt32.valueOf(32)).intValue()).or(
+        var.shiftRight(UInt32.valueOf(32).subtract(hops.mod(UInt32.valueOf(32))).intValue()));
+  }
+
+  static UInt32 rotr32(UInt32 var, UInt32 hops) {
+    return var.shiftRight(hops.mod(UInt32.valueOf(32)).intValue()).or(
+        var.shiftLeft(UInt32.valueOf(32).subtract(hops.mod(UInt32.valueOf(32))).intValue()));
+  }
+}
diff --git a/progpow/src/main/java/org/apache/tuweni/progpow/package-info.java b/progpow/src/main/java/org/apache/tuweni/progpow/package-info.java
new file mode 100644
index 0000000..ced102b
--- /dev/null
+++ b/progpow/src/main/java/org/apache/tuweni/progpow/package-info.java
@@ -0,0 +1,9 @@
+/**
+ * Ethereum ProgPoW mining implementation.
+ *
+ * Code derived from code written by Danno Ferrin under the Pantheon client, under ASF license.
+ */
+@ParametersAreNonnullByDefault
+package org.apache.tuweni.progpow;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/progpow/src/test/java/org/apache/tuweni/progpow/FillMixTest.java b/progpow/src/test/java/org/apache/tuweni/progpow/FillMixTest.java
new file mode 100644
index 0000000..9b07316
--- /dev/null
+++ b/progpow/src/test/java/org/apache/tuweni/progpow/FillMixTest.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
+ * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
+ * to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.tuweni.progpow;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.apache.tuweni.units.bigints.UInt32;
+import org.apache.tuweni.units.bigints.UInt64;
+
+import org.junit.jupiter.api.Test;
+
+class FillMixTest {
+
+  @Test
+  void testGenerateMix() {
+    UInt32[] mix = ProgPoW.fillMix(UInt64.fromHexString("0xEE304846DDD0A47B"), UInt32.valueOf(0));
+    String[] expectedValues = new String[] {
+        "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"};
+    assertEquals(expectedValues.length, mix.length);
+
+    for (int i = 0; i < expectedValues.length; i++) {
+      assertEquals(UInt32.fromHexString(expectedValues[i]), mix[i]);
+    }
+  }
+
+  @Test
+  void testGenerateMixDifferentLane() {
+    UInt32[] mix = ProgPoW.fillMix(UInt64.fromHexString("0xEE304846DDD0A47B"), UInt32.valueOf(13));
+    String[] expectedValues = new String[] {
+        "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"};
+    assertEquals(expectedValues.length, mix.length);
+
+    for (int i = 0; i < expectedValues.length; i++) {
+      assertEquals(UInt32.fromHexString(expectedValues[i]), mix[i]);
+    }
+  }
+}
diff --git a/progpow/src/test/java/org/apache/tuweni/progpow/Fnv1aTest.java b/progpow/src/test/java/org/apache/tuweni/progpow/Fnv1aTest.java
new file mode 100644
index 0000000..0f3f6ed
--- /dev/null
+++ b/progpow/src/test/java/org/apache/tuweni/progpow/Fnv1aTest.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
+ * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
+ * to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.tuweni.progpow;
+
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.apache.tuweni.units.bigints.UInt32;
+
+import java.util.stream.Stream;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * Tests for https://github.com/ifdefelse/ProgPOW/blob/master/test-vectors.md#fnv1a
+ */
+class Fnv1aTest {
+
+  @ParameterizedTest()
+  @MethodSource("vectorSupplier")
+  void testVector(UInt32 h, UInt32 d, UInt32 expected) {
+    UInt32 result = ProgPoW.fnv1a(h, d);
+    assertEquals(expected, result);
+  }
+
+
+  private static Stream<Arguments> vectorSupplier() {
+    return Stream.of(
+        Arguments.arguments(
+            UInt32.fromHexString("0x811C9DC5"),
+            UInt32.fromHexString("0xDDD0A47B"),
+            UInt32.fromHexString("0xD37EE61A")));
+  }
+
+}
diff --git a/progpow/src/test/java/org/apache/tuweni/progpow/KISS99RandomTest.java b/progpow/src/test/java/org/apache/tuweni/progpow/KISS99RandomTest.java
new file mode 100644
index 0000000..901ac44
--- /dev/null
+++ b/progpow/src/test/java/org/apache/tuweni/progpow/KISS99RandomTest.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
+ * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
+ * to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.tuweni.progpow;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.apache.tuweni.units.bigints.UInt32;
+
+import org.junit.jupiter.api.Test;
+
+class KISS99RandomTest {
+
+  @Test
+  void testRounds() {
+    KISS99Random random = new KISS99Random(
+        UInt32.valueOf(362436069),
+        UInt32.valueOf(521288629),
+        UInt32.valueOf(123456789),
+        UInt32.valueOf(380116160));
+    assertEquals(UInt32.valueOf(769445856), random.generate());
+    assertEquals(UInt32.valueOf(742012328), random.generate());
+    assertEquals(UInt32.valueOf(2121196314), random.generate());
+    assertEquals(UInt32.fromHexString("0xa73a60ce"), random.generate());
+    for (int i = 0; i < 99995; i++) {
+      random.generate();
+    }
+    assertEquals(UInt32.valueOf(941074834), random.generate());
+  }
+}
diff --git a/progpow/src/test/java/org/apache/tuweni/progpow/Keccakf800Test.java b/progpow/src/test/java/org/apache/tuweni/progpow/Keccakf800Test.java
new file mode 100644
index 0000000..669c536
--- /dev/null
+++ b/progpow/src/test/java/org/apache/tuweni/progpow/Keccakf800Test.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
+ * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
+ * to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.tuweni.progpow;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.apache.tuweni.bytes.Bytes;
+import org.apache.tuweni.bytes.Bytes32;
+
+import java.nio.ByteBuffer;
+import java.util.stream.IntStream;
+
+import org.junit.jupiter.api.Test;
+
+class Keccakf800Test {
+
+  @Test
+  void testKeccak() {
+    int[] vector =
+        new int[] {0xCCDDEEFF, 0x8899AABB, 0x44556677, 0x00112233, 0x33221100, 0x77665544, 0xBBAA9988, 0xFFEEDDCC};
+    Bytes32 test = Bytes32.wrap(
+        Bytes.concatenate(
+            IntStream.of(vector).mapToObj(i -> Bytes.wrap(ByteBuffer.allocate(4).putInt(i).array()).reverse()).toArray(
+                Bytes[]::new)));
+
+    int[] expectedResult =
+        new int[] {0x464830EE, 0x7BA4D0DD, 0x969E1798, 0xCEC50EB6, 0x7872E2EA, 0x597E3634, 0xE380E73D, 0x2F89D1E6};
+    Bytes32 expectedResultBytes = Bytes32.wrap(
+        Bytes.concatenate(
+            IntStream
+                .of(expectedResult)
+                .mapToObj(i -> Bytes.wrap(ByteBuffer.allocate(4).putInt(i).array()).reverse())
+                .toArray(Bytes[]::new)));
+
+    Bytes32 result = Keccakf800.keccakF800Progpow(test, 0x123456789ABCDEF0L, Bytes32.ZERO);
+    assertEquals(expectedResultBytes, result);
+  }
+
+  @Test
+  void testKeccakWithDigest() {
+    int[] vector =
+        new int[] {0xCCDDEEFF, 0x8899AABB, 0x44556677, 0x00112233, 0x33221100, 0x77665544, 0xBBAA9988, 0xFFEEDDCC};
+    Bytes32 test = Bytes32.wrap(
+        Bytes.concatenate(
+            IntStream.of(vector).mapToObj(i -> Bytes.wrap(ByteBuffer.allocate(4).putInt(i).array()).reverse()).toArray(
+                Bytes[]::new)));
+    int[] expectedResult =
+        new int[] {0x47CD7C5B, 0xD9FDBE2D, 0xAC5C895B, 0xFF67CE8E, 0x6B5AEB0D, 0xE1C6ECD2, 0x003D3862, 0xCE8E72C3};
+    Bytes32 expectedResultBytes = Bytes32.wrap(
+        Bytes.concatenate(
+            IntStream
+                .of(expectedResult)
+                .mapToObj(i -> Bytes.wrap(ByteBuffer.allocate(4).putInt(i).array()).reverse())
+                .toArray(Bytes[]::new)));
+
+    Bytes32 result = Keccakf800.keccakF800Progpow(
+        test,
+        0xEE304846DDD0A47BL,
+        Bytes32.fromHexString("0x0598F11166B48AC5719CFF105F0ACF9D162FFA18EF8E790521470C777D767492"));
+    assertEquals(expectedResultBytes, result);
+  }
+}
diff --git a/progpow/src/test/java/org/apache/tuweni/progpow/MergeTest.java b/progpow/src/test/java/org/apache/tuweni/progpow/MergeTest.java
new file mode 100644
index 0000000..4a1ac42
--- /dev/null
+++ b/progpow/src/test/java/org/apache/tuweni/progpow/MergeTest.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
+ * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
+ * to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.tuweni.progpow;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.apache.tuweni.units.bigints.UInt32;
+
+import java.util.stream.Stream;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class MergeTest {
+
+  @ParameterizedTest
+  @MethodSource("mergeVectors")
+  void testMerge(UInt32 a, UInt32 b, UInt32 r, UInt32 result, String path) {
+    assertEquals(result, ProgPoW.merge(a, b, r));
+  }
+
+  private static Stream<Arguments> mergeVectors() {
+    return Stream.of(
+        Arguments.of(
+            UInt32.fromHexString("0x3B0BB37D"),
+            UInt32.fromHexString("0xA0212004"),
+            UInt32.fromHexString("0x9BD26AB0"),
+            UInt32.fromHexString("0x3CA34321"),
+            "mul/add"),
+        Arguments.of(
+            UInt32.fromHexString("0x10C02F0D"),
+            UInt32.fromHexString("0x870FA227"),
+            UInt32.fromHexString("0xD4F45515"),
+            UInt32.fromHexString("0x91C1326A"),
+            "xor/mul"),
+        Arguments.of(
+            UInt32.fromHexString("0x24D2BAE4"),
+            UInt32.fromHexString("0x0FFB4C9B"),
+            UInt32.fromHexString("0x7FDBC2F2"),
+            UInt32.fromHexString("0x2EDDD94C"),
+            "rotl/xor"),
+        Arguments.of(
+            UInt32.fromHexString("0xDA39E821"),
+            UInt32.fromHexString("0x089C4008"),
+            UInt32.fromHexString("0x8B6CD8C3"),
+            UInt32.fromHexString("0x8A81E396"),
+            "rotr/xor"));
+  }
+}
diff --git a/progpow/src/test/java/org/apache/tuweni/progpow/ProgPoWMathTest.java b/progpow/src/test/java/org/apache/tuweni/progpow/ProgPoWMathTest.java
new file mode 100644
index 0000000..22c15b5
--- /dev/null
+++ b/progpow/src/test/java/org/apache/tuweni/progpow/ProgPoWMathTest.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
+ * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
+ * to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.tuweni.progpow;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.apache.tuweni.units.bigints.UInt32;
+
+import java.util.stream.Stream;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+public class ProgPoWMathTest {
+
+  @ParameterizedTest
+  @MethodSource("mergeVectors")
+  void testMath(UInt32 a, UInt32 b, UInt32 r, UInt32 expected, String path) {
+    UInt32 result = ProgPoWMath.math(a, b, r);
+    assertEquals(expected, result, expected.toHexString() + " <> " + result.toHexString());
+  }
+
+  private static Stream<Arguments> mergeVectors() {
+    return Stream.of(
+        Arguments.of(
+            UInt32.fromHexString("0x8626BB1F"),
+            UInt32.fromHexString("0xBBDFBC4E"),
+            UInt32.fromHexString("0x883E5B49"),
+            UInt32.fromHexString("0x4206776D"),
+            "add"),
+        Arguments.of(
+            UInt32.fromHexString("0x3F4BDFAC"),
+            UInt32.fromHexString("0xD79E414F"),
+            UInt32.fromHexString("0x36B71236"),
+            UInt32.fromHexString("0x4C5CB214"),
+            "mul"),
+        Arguments.of(
+            UInt32.fromHexString("0x6D175B7E"),
+            UInt32.fromHexString("0xC4E89D4C"),
+            UInt32.fromHexString("0x944ECABB"),
+            UInt32.fromHexString("0x53E9023F"),
+            "mul_hi32"),
+        Arguments.of(
+            UInt32.fromHexString("0x2EDDD94C"),
+            UInt32.fromHexString("0x7E70CB54"),
+            UInt32.fromHexString("0x3F472A85"),
+            UInt32.fromHexString("0x2EDDD94C"),
+            "min"),
+        Arguments.of(
+            UInt32.fromHexString("0x61AE0E62"),
+            UInt32.fromHexString("0xe0596b32"),
+            UInt32.fromHexString("0x3F472A85"),
+            UInt32.fromHexString("0x61AE0E62"),
+            "min again (unsigned)"),
+        Arguments.of(
+            UInt32.fromHexString("0x8A81E396"),
+            UInt32.fromHexString("0x3F4BDFAC"),
+            UInt32.fromHexString("0xCEC46E67"),
+            UInt32.fromHexString("0x1E3968A8"),
+            "rotl32"),
+        Arguments.of(
+            UInt32.fromHexString("0x8A81E396"),
+            UInt32.fromHexString("0x7E70CB54"),
+            UInt32.fromHexString("0xDBE71FF7"),
+            UInt32.fromHexString("0x1E3968A8"),
+            "rotr32"),
+        Arguments.of(
+            UInt32.fromHexString("0xA7352F36"),
+            UInt32.fromHexString("0xA0EB7045"),
+            UInt32.fromHexString("0x59E7B9D8"),
+            UInt32.fromHexString("0xA0212004"),
+            "bitwise and"),
+        Arguments.of(
+            UInt32.fromHexString("0xC89805AF"),
+            UInt32.fromHexString("0x64291E2F"),
+            UInt32.fromHexString("0x1BDC84A9"),
+            UInt32.fromHexString("0xECB91FAF"),
+            "bitwise or"),
+        Arguments.of(
+            UInt32.fromHexString("0x760726D3"),
+            UInt32.fromHexString("0x79FC6A48"),
+            UInt32.fromHexString("0xC675CAC5"),
+            UInt32.fromHexString("0x0FFB4C9B"),
+            "bitwise xor"),
+        Arguments.of(
+            UInt32.fromHexString("0x75551D43"),
+            UInt32.fromHexString("0x3383BA34"),
+            UInt32.fromHexString("0x2863AD31"),
+            UInt32.fromHexString("0x00000003"),
+            "clz (leading zeros)"),
+        Arguments.of(
+            UInt32.fromHexString("0xEA260841"),
+            UInt32.fromHexString("0xE92C44B7"),
+            UInt32.fromHexString("0xF83FFE7D"),
+            UInt32.fromHexString("0x0000001B"),
+            "popcount (number of 1s)"));
+  }
+}
diff --git a/progpow/src/test/java/org/apache/tuweni/progpow/ProgPoWTest.java b/progpow/src/test/java/org/apache/tuweni/progpow/ProgPoWTest.java
new file mode 100644
index 0000000..cacebae
--- /dev/null
+++ b/progpow/src/test/java/org/apache/tuweni/progpow/ProgPoWTest.java
@@ -0,0 +1,743 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
+ * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
+ * to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.tuweni.progpow;
+
+import static org.apache.tuweni.progpow.ProgPoW.PROGPOW_REGS;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.apache.tuweni.bytes.Bytes;
+import org.apache.tuweni.bytes.Bytes32;
+import org.apache.tuweni.ethash.EthHash;
+import org.apache.tuweni.junit.BouncyCastleExtension;
+import org.apache.tuweni.units.bigints.UInt32;
+import org.apache.tuweni.units.bigints.UInt64;
+
+import java.util.stream.Stream;
+
+import com.google.common.primitives.Ints;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@ExtendWith(BouncyCastleExtension.class)
+class ProgPoWTest {
+
+  @Test
+  void testProgPoWInit() {
+    int[] src = new int[PROGPOW_REGS];
+    int[] dest = new int[PROGPOW_REGS];
+    KISS99Random random = ProgPoW.progPowInit(UInt64.valueOf(600), src, dest);
+    int[] expectedSrc = new int[] {
+        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};
+    int[] expectedDest = new int[] {
+        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};
+
+    assertEquals(random.z, UInt32.fromHexString("0x6535921C"));
+    assertEquals(random.w, UInt32.fromHexString("0x29345B16"));
+    assertEquals(random.jsr, UInt32.fromHexString("0xC0DD7F78"));
+    assertEquals(random.jcong, UInt32.fromHexString("0x1165D7EB"));
+    assertArrayEquals(expectedSrc, src);
+    assertArrayEquals(expectedDest, dest);
+  }
+
+  @Test
+  void testEthHashCalc() {
+    long blockNumber = 30000;
+    long epochIndex = EthHash.epoch(30000);
+
+    UInt32[] cache = EthHash.mkCache(Ints.checkedCast(EthHash.getCacheSize(blockNumber)), blockNumber);
+    Bytes expected = Bytes.fromHexString(
+        "0x6754e3b3e30274ae82a722853b35d8a2bd2347ffee05bcbfde4469deb8b5d2f0d3ba4cc797f700b1be60bc050b84404f6872e43593f9d69c59460e6a6ee438b8");
+
+    assertEquals(expected, EthHash.calcDatasetItem(cache, 0));
+  }
+
+  @Test
+  public void progPowLoop() {
+    UInt64 seed = UInt64.fromHexString("0xEE304846DDD0A47B");
+    // initialize mix for all lanes
+    UInt32[][] mix = new UInt32[ProgPoW.PROGPOW_LANES][ProgPoW.PROGPOW_REGS];
+    for (int l = 0; l < ProgPoW.PROGPOW_LANES; l++) {
+      mix[l] = ProgPoW.fillMix(seed, UInt32.valueOf(l));
+    }
+
+    long blockNumber = 30000;
+
+    UInt32[] cache = EthHash.mkCache(Ints.checkedCast(EthHash.getCacheSize(blockNumber)), blockNumber);
+    UInt32[] cDag = ProgPoW.createDagCache(blockNumber, (ind) -> EthHash.calcDatasetItem(cache, ind));
+
+    ProgPoW.progPowLoop(blockNumber, UInt32.ZERO, mix, cDag, (ind) -> EthHash.calcDatasetItem(cache, ind));
+
+    for (int i = 0; i < mix[0].length; i++) {
+      System.out.println(mix[0][i]);
+    }
+
+    assertArrayEquals(
+        mix[0],
+        fill(
+            new String[] {
+                "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"}));
+
+    assertArrayEquals(
+        mix[1],
+        fill(
+            new String[] {
+                "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"}));
+    assertArrayEquals(
+        mix[2],
+        fill(
+            new String[] {
+                "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"}));
+    assertArrayEquals(
+        mix[3],
+        fill(
+            new String[] {
+                "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"}));
+    assertArrayEquals(
+        mix[4],
+        fill(
+            new String[] {
+                "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"}));
+    assertArrayEquals(
+        mix[5],
+        fill(
+            new String[] {
+                "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"}));
+    assertArrayEquals(
+        mix[6],
+        fill(
+            new String[] {
+                "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"}));
+    assertArrayEquals(
+        mix[7],
+        fill(
+            new String[] {
+                "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"}));
+    assertArrayEquals(
+        mix[8],
+        fill(
+            new String[] {
+                "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"}));
+    assertArrayEquals(
+        mix[9],
+        fill(
+            new String[] {
+                "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"}));
+    assertArrayEquals(
+        mix[10],
+        fill(
+            new String[] {
+                "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"}));
+    assertArrayEquals(
+        mix[11],
+        fill(
+            new String[] {
+                "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"}));
+    assertArrayEquals(
+        mix[12],
+        fill(
+            new String[] {
+                "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"}));
+    assertArrayEquals(
+        mix[13],
+        fill(
+            new String[] {
+                "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"}));
+    assertArrayEquals(
+        mix[14],
+        fill(
+            new String[] {
+                "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"}));
+    assertArrayEquals(
+        mix[15],
+        fill(
+            new String[] {
+                "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"}));
+  }
+
+  private UInt32[] fill(String[] elts) {
+    return Stream.of(elts).map(UInt32::fromHexString).toArray(UInt32[]::new);
+  }
+
+  @Test
+  void testProgPoWHash() {
+    long blockNumber = 30000;
+    UInt32[] cache = EthHash.mkCache(Ints.checkedCast(EthHash.getCacheSize(blockNumber)), blockNumber);
+    UInt32[] cDag = ProgPoW.createDagCache(blockNumber, (ind) -> EthHash.calcDatasetItem(cache, ind));
+    Bytes32 digest = ProgPoW.progPowHash(
+        blockNumber,
+        0x123456789abcdef0L,
+        Bytes32.fromHexString("ffeeddccbbaa9988776655443322110000112233445566778899aabbccddeeff"),
+        cDag,
+        (ind) -> EthHash.calcDatasetItem(cache, ind));
+    assertEquals(Bytes32.fromHexString("5b7ccd472dbefdd95b895cac8ece67ff0deb5a6bd2ecc6e162383d00c3728ece"), digest);
+  }
+}
diff --git a/settings.gradle b/settings.gradle
index f0e1d0c..27d3a09 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -18,6 +18,7 @@ include 'merkle-trie'
 include 'net'
 include 'net-coroutines'
 include 'plumtree'
+include 'progpow'
 include 'rlp'
 include 'rlpx'
 include 'scuttlebutt'


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tuweni.apache.org
For additional commands, e-mail: commits-help@tuweni.apache.org