This is an automated email from the ASF dual-hosted git repository. toulmean pushed a commit to branch 1.0 in repository https://gitbox.apache.org/repos/asf/incubator-tuweni.git
commit 7706bf200f5602f590cb9e6322562d8e91a60477 Author: Antoine Toulme <[email protected]> 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: [email protected] For additional commands, e-mail: [email protected]
