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