diff --git a/crypto/src/main/java/tech/pegasys/pantheon/crypto/Blake2bfMessageDigest.java b/crypto/src/main/java/tech/pegasys/pantheon/crypto/Blake2bfMessageDigest.java new file mode 100644 index 0000000000..7ee8992a91 --- /dev/null +++ b/crypto/src/main/java/tech/pegasys/pantheon/crypto/Blake2bfMessageDigest.java @@ -0,0 +1,283 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * Licensed 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 tech.pegasys.pantheon.crypto; + +import static java.util.Arrays.copyOfRange; + +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.jcajce.provider.digest.BCMessageDigest; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.Pack; + +public class Blake2bfMessageDigest extends BCMessageDigest { + + public Blake2bfMessageDigest() { + super(new Blake2bfDigest()); + } + + /** + * Implementation of the `F` compression function of the Blake2b cryptographic hash function. + * + *
RFC - https://tools.ietf.org/html/rfc7693 + * + *
Adapted from - https://github.com/keep-network/blake2b/blob/master/compression/f.go + * + *
Optimized for 64-bit platforms + */ + public static class Blake2bfDigest implements Digest { + + public static final int MESSAGE_LENGTH_BYTES = 213; + + private static final long[] IV = { + 0x6a09e667f3bcc908L, 0xbb67ae8584caa73bL, 0x3c6ef372fe94f82bL, + 0xa54ff53a5f1d36f1L, 0x510e527fade682d1L, 0x9b05688c2b3e6c1fL, + 0x1f83d9abfb41bd6bL, 0x5be0cd19137e2179L + }; + + private static final byte[][] PRECOMPUTED = { + {0, 2, 4, 6, 1, 3, 5, 7, 8, 10, 12, 14, 9, 11, 13, 15}, + {14, 4, 9, 13, 10, 8, 15, 6, 1, 0, 11, 5, 12, 2, 7, 3}, + {11, 12, 5, 15, 8, 0, 2, 13, 10, 3, 7, 9, 14, 6, 1, 4}, + {7, 3, 13, 11, 9, 1, 12, 14, 2, 5, 4, 15, 6, 10, 0, 8}, + {9, 5, 2, 10, 0, 7, 4, 15, 14, 11, 6, 3, 1, 12, 8, 13}, + {2, 6, 0, 8, 12, 10, 11, 3, 4, 7, 15, 1, 13, 5, 14, 9}, + {12, 1, 14, 4, 5, 15, 13, 10, 0, 6, 9, 8, 7, 3, 2, 11}, + {13, 7, 12, 3, 11, 14, 1, 9, 5, 15, 8, 2, 0, 4, 6, 10}, + {6, 14, 11, 0, 15, 9, 3, 8, 12, 13, 1, 10, 2, 7, 4, 5}, + {10, 8, 7, 1, 2, 4, 6, 5, 15, 9, 3, 13, 11, 14, 12, 0} + }; + + private static final int DIGEST_LENGTH = 64; + + // buffer which holds serialized input for this compression function + // [ 4 bytes for rounds ][ 64 bytes for h ][ 128 bytes for m ] + // [ 8 bytes for t_0 ][ 8 bytes for t_1 ][ 1 byte for f ] + private final byte[] buffer; + + private int bufferPos; + + // deserialized inputs for f compression + private final long[] h; + private final long[] m; + private final long[] t; + private boolean f; + private long rounds; // unsigned integer represented as long + + private final long[] v; + + Blake2bfDigest() { + buffer = new byte[MESSAGE_LENGTH_BYTES]; + bufferPos = 0; + + h = new long[8]; + m = new long[16]; + t = new long[2]; + f = false; + rounds = 12; + + v = new long[16]; + } + + // for tests + Blake2bfDigest( + final long[] h, final long[] m, final long[] t, final boolean f, final long rounds) { + assert rounds <= 4294967295L; // uint max value + buffer = new byte[MESSAGE_LENGTH_BYTES]; + bufferPos = 0; + + this.h = h; + this.m = m; + this.t = t; + this.f = f; + this.rounds = rounds; + + v = new long[16]; + } + + @Override + public String getAlgorithmName() { + return "BLAKE2f"; + } + + @Override + public int getDigestSize() { + return DIGEST_LENGTH; + } + + /** + * update the message digest with a single byte. + * + * @param in the input byte to be entered. + */ + @Override + public void update(final byte in) { + + if (bufferPos == MESSAGE_LENGTH_BYTES) { // full buffer + throw new IllegalArgumentException(); + } else { + buffer[bufferPos] = in; + bufferPos++; + if (bufferPos == MESSAGE_LENGTH_BYTES) { + initialize(); + } + } + } + + /** + * update the message digest with a block of bytes. + * + * @param in the byte array containing the data. + * @param offset the offset into the byte array where the data starts. + * @param len the length of the data. + */ + @Override + public void update(final byte[] in, final int offset, final int len) { + if (in == null || len == 0) { + return; + } + + if (len > MESSAGE_LENGTH_BYTES - bufferPos) { + throw new IllegalArgumentException( + "Attempting to update buffer with " + + len + + " byte(s) but there is " + + (MESSAGE_LENGTH_BYTES - bufferPos) + + " byte(s) left to fill"); + } + + System.arraycopy(in, offset, buffer, bufferPos, len); + + bufferPos += len; + + if (bufferPos == MESSAGE_LENGTH_BYTES) { + initialize(); + } + } + + /** + * close the digest, producing the final digest value. The doFinal call leaves the digest reset. + * + * @param out the array the digest is to be copied into. + * @param offset the offset into the out array the digest is to start at. + */ + @Override + public int doFinal(final byte[] out, final int offset) { + if (bufferPos != 213) { + throw new IllegalStateException("The buffer must be filled with 213 bytes"); + } + + compress(); + + for (int i = 0; i < h.length; i++) { + System.arraycopy(Pack.longToBigEndian(h[i]), 0, out, i * 8, 8); + } + + reset(); + + return 0; + } + + /** Reset the digest back to it's initial state. */ + @Override + public void reset() { + bufferPos = 0; + Arrays.fill(buffer, (byte) 0); + Arrays.fill(h, 0); + Arrays.fill(m, (byte) 0); + Arrays.fill(t, 0); + f = false; + rounds = 12; + Arrays.fill(v, 0); + } + + private void initialize() { + rounds = Integer.toUnsignedLong(bytesToInt(copyOfRange(buffer, 0, 4))); + + for (int i = 0; i < h.length; i++) { + final int offset = 4 + i * 8; + h[i] = bytesToLong((copyOfRange(buffer, offset, offset + 8))); + } + + for (int i = 0; i < 16; i++) { + final int offset = 68 + i * 8; + m[i] = bytesToLong(copyOfRange(buffer, offset, offset + 8)); + } + + t[0] = bytesToLong(copyOfRange(buffer, 196, 204)); + t[1] = bytesToLong(copyOfRange(buffer, 204, 212)); + + f = buffer[212] != 0; + } + + private int bytesToInt(final byte[] bytes) { + return Pack.bigEndianToInt(bytes, 0); + } + + private long bytesToLong(final byte[] bytes) { + return Pack.bigEndianToLong(bytes, 0); + } + + /** + * F is a compression function for BLAKE2b. It takes as an argument the state vector `h`, + * message block vector `m`, offset counter `t`, final block indicator flag `f`, and number of + * rounds `rounds`. The state vector provided as the first parameter is modified by the + * function. + */ + private void compress() { + + long t0 = t[0]; + long t1 = t[1]; + + System.arraycopy(h, 0, v, 0, 8); + System.arraycopy(IV, 0, v, 8, 8); + + v[12] ^= t0; + v[13] ^= t1; + + if (f) { + v[14] ^= 0xffffffffffffffffL; + } + + for (long j = 0; j < rounds; ++j) { + byte[] s = PRECOMPUTED[(int) (j % 10)]; + + mix(m[s[0]], m[s[4]], 0, 4, 8, 12); + mix(m[s[1]], m[s[5]], 1, 5, 9, 13); + mix(m[s[2]], m[s[6]], 2, 6, 10, 14); + mix(m[s[3]], m[s[7]], 3, 7, 11, 15); + mix(m[s[8]], m[s[12]], 0, 5, 10, 15); + mix(m[s[9]], m[s[13]], 1, 6, 11, 12); + mix(m[s[10]], m[s[14]], 2, 7, 8, 13); + mix(m[s[11]], m[s[15]], 3, 4, 9, 14); + } + + // update h: + for (int offset = 0; offset < h.length; offset++) { + h[offset] ^= v[offset] ^ v[offset + 8]; + } + } + + private void mix( + final long a, final long b, final int i, final int j, final int k, final int l) { + v[i] += a + v[j]; + v[l] = Long.rotateLeft(v[l] ^ v[i], -32); + v[k] += v[l]; + v[j] = Long.rotateLeft(v[j] ^ v[k], -24); + + v[i] += b + v[j]; + v[l] = Long.rotateLeft(v[l] ^ v[i], -16); + v[k] += v[l]; + v[j] = Long.rotateLeft(v[j] ^ v[k], -63); + } + } +} diff --git a/crypto/src/main/java/tech/pegasys/pantheon/crypto/Hash.java b/crypto/src/main/java/tech/pegasys/pantheon/crypto/Hash.java index 182aaa1d41..23fc0dab77 100644 --- a/crypto/src/main/java/tech/pegasys/pantheon/crypto/Hash.java +++ b/crypto/src/main/java/tech/pegasys/pantheon/crypto/Hash.java @@ -17,22 +17,16 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.security.Security; - -import org.bouncycastle.jce.provider.BouncyCastleProvider; /** Various utilities for providing hashes (digests) of arbitrary data. */ public abstract class Hash { private Hash() {} - static { - Security.addProvider(new BouncyCastleProvider()); - } - public static final String KECCAK256_ALG = "KECCAK-256"; private static final String SHA256_ALG = "SHA-256"; private static final String RIPEMD160 = "RIPEMD160"; + private static final String BLAKE2BF_ALG = "BLAKE2BF"; /** * Helper method to generate a digest using the provided algorithm. @@ -42,9 +36,8 @@ private Hash() {} * @return A digest. */ private static byte[] digestUsingAlgorithm(final BytesValue input, final String alg) { - final MessageDigest digest; try { - digest = BouncyCastleMessageDigestFactory.create(alg); + final MessageDigest digest = MessageDigestFactory.create(alg); input.update(digest); return digest.digest(); } catch (final NoSuchAlgorithmException e) { @@ -81,4 +74,14 @@ public static Bytes32 keccak256(final BytesValue input) { public static BytesValue ripemd160(final BytesValue input) { return BytesValue.wrap(digestUsingAlgorithm(input, RIPEMD160)); } + + /** + * Digest using Blake2f compression function. + * + * @param input The input bytes to produce the digest for. + * @return A digest. + */ + public static BytesValue blake2bf(final BytesValue input) { + return BytesValue.wrap(digestUsingAlgorithm(input, BLAKE2BF_ALG)); + } } diff --git a/crypto/src/main/java/tech/pegasys/pantheon/crypto/BouncyCastleMessageDigestFactory.java b/crypto/src/main/java/tech/pegasys/pantheon/crypto/MessageDigestFactory.java similarity index 79% rename from crypto/src/main/java/tech/pegasys/pantheon/crypto/BouncyCastleMessageDigestFactory.java rename to crypto/src/main/java/tech/pegasys/pantheon/crypto/MessageDigestFactory.java index ad14d56bfd..28efadec66 100644 --- a/crypto/src/main/java/tech/pegasys/pantheon/crypto/BouncyCastleMessageDigestFactory.java +++ b/crypto/src/main/java/tech/pegasys/pantheon/crypto/MessageDigestFactory.java @@ -14,15 +14,19 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.security.Security; import org.bouncycastle.jce.provider.BouncyCastleProvider; -public class BouncyCastleMessageDigestFactory { +public class MessageDigestFactory { - private static final BouncyCastleProvider securityProvider = new BouncyCastleProvider(); + static { + Security.addProvider(new PantheonProvider()); + Security.addProvider(new BouncyCastleProvider()); + } @SuppressWarnings("DoNotInvokeMessageDigestDirectly") public static MessageDigest create(final String algorithm) throws NoSuchAlgorithmException { - return MessageDigest.getInstance(algorithm, securityProvider); + return MessageDigest.getInstance(algorithm); } } diff --git a/crypto/src/main/java/tech/pegasys/pantheon/crypto/PantheonProvider.java b/crypto/src/main/java/tech/pegasys/pantheon/crypto/PantheonProvider.java new file mode 100644 index 0000000000..d769083e43 --- /dev/null +++ b/crypto/src/main/java/tech/pegasys/pantheon/crypto/PantheonProvider.java @@ -0,0 +1,35 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * Licensed 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 tech.pegasys.pantheon.crypto; + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.security.Provider; + +public final class PantheonProvider extends Provider { + + private static final String info = "Pantheon Security Provider v1.0"; + + public static final String PROVIDER_NAME = "Pantheon"; + + @SuppressWarnings("unchecked") + public PantheonProvider() { + super(PROVIDER_NAME, "1.0", info); + AccessController.doPrivileged( + (PrivilegedAction) + () -> { + put("MessageDigest.Blake2bf", "tech.pegasys.pantheon.crypto.Blake2bfMessageDigest"); + return null; + }); + } +} diff --git a/crypto/src/test/java/tech/pegasys/pantheon/crypto/Blake2bfMessageDigestTest.java b/crypto/src/test/java/tech/pegasys/pantheon/crypto/Blake2bfMessageDigestTest.java new file mode 100644 index 0000000000..7c1a218cf5 --- /dev/null +++ b/crypto/src/test/java/tech/pegasys/pantheon/crypto/Blake2bfMessageDigestTest.java @@ -0,0 +1,112 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * Licensed 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 tech.pegasys.pantheon.crypto; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.bouncycastle.util.Pack; +import org.junit.Before; +import org.junit.Test; + +/** + * Test vectors adapted from + * https://github.com/keep-network/blake2b/blob/master/compression/f_test.go + */ +public class Blake2bfMessageDigestTest { + + private Blake2bfMessageDigest messageDigest; + + // output when input is all 0 + private byte[] blake2bfAllZero = + new byte[] { + 106, 9, -26, 103, -13, -68, -55, 8, -69, 103, -82, -123, -124, -54, -89, 59, 60, 110, -13, + 114, -2, -108, -8, 43, -91, 79, -11, 58, 95, 29, 54, -15, 81, 14, 82, 127, -83, -26, -126, + -47, -101, 5, 104, -116, 43, 62, 108, 31, 31, -125, -39, -85, -5, 65, -67, 107, 91, -32, + -51, 25, 19, 126, 33, 121 + }; + + // output when input is all 0 for 2147483648 rounds + private byte[] blake2bfAllZeroNegativeRounds = + new byte[] { + 118, 127, 109, 29, 115, -124, -99, -111, 81, 112, 35, 60, -89, 75, 21, 18, -97, -73, 19, + -102, 40, -8, 78, 110, -43, 124, 66, 83, -89, 69, 69, 57, -25, -105, 123, 117, 115, 115, 78, + -92, 123, 87, 14, -127, -94, -1, -74, 25, -125, 48, 54, -78, -82, -75, 84, -26, -38, -42, + -93, 120, -61, 7, -58, 38 + }; + + @Before + public void setUp() { + messageDigest = new Blake2bfMessageDigest(); + } + + @Test + public void digestIfUpdatedCorrectlyWithBytes() { + for (int i = 0; i < 213; i++) { + messageDigest.update((byte) 0); + } + assertThat(messageDigest.digest()).isEqualTo(blake2bfAllZero); + } + + @Test + public void digestIfUpdatedCorrectlyWithByteArray() { + byte[] update = new byte[213]; + messageDigest.update(update, 0, 213); + assertThat(messageDigest.digest()).isEqualTo(blake2bfAllZero); + } + + @Test + public void digestIfUpdatedCorrectlyMixed() { + byte[] update = new byte[213]; + messageDigest.update((byte) 0); + messageDigest.update(update, 2, 211); + messageDigest.update((byte) 0); + assertThat(messageDigest.digest()).isEqualTo(blake2bfAllZero); + } + + @Test + public void digestWithNegativeRounds() { + // equal to Integer.MAX_VALUE + 1 (2147483648) as uint + byte[] rounds = Pack.intToBigEndian(Integer.MIN_VALUE); + messageDigest.update(rounds, 0, 4); + messageDigest.update(new byte[213], 0, 209); + assertThat(messageDigest.digest()).isEqualTo(blake2bfAllZeroNegativeRounds); + } + + @Test(expected = IllegalStateException.class) + public void throwsIfBufferUpdatedWithLessThat213Bytes() { + for (int i = 0; i < 212; i++) { + messageDigest.update((byte) 0); + } + messageDigest.digest(); + } + + @Test(expected = IllegalArgumentException.class) + public void throwsIfBufferUpdatedWithMoreThat213Bytes() { + for (int i = 0; i < 214; i++) { + messageDigest.update((byte) 0); + } + } + + @Test(expected = IllegalArgumentException.class) + public void throwsIfBufferUpdatedLargeByteArray() { + byte[] update = new byte[213]; + messageDigest.update((byte) 0); + messageDigest.update(update, 0, 213); + } + + @Test(expected = IllegalArgumentException.class) + public void throwsIfEmptyBufferUpdatedLargeByteArray() { + byte[] update = new byte[214]; + messageDigest.update(update, 0, 214); + } +} diff --git a/crypto/src/test/java/tech/pegasys/pantheon/crypto/HashTest.java b/crypto/src/test/java/tech/pegasys/pantheon/crypto/HashTest.java index 12bed28bdd..d6e31f6ee3 100644 --- a/crypto/src/test/java/tech/pegasys/pantheon/crypto/HashTest.java +++ b/crypto/src/test/java/tech/pegasys/pantheon/crypto/HashTest.java @@ -17,6 +17,7 @@ import tech.pegasys.pantheon.util.bytes.BytesValue; +import org.bouncycastle.util.encoders.Hex; import org.junit.Test; public class HashTest { @@ -26,6 +27,12 @@ public class HashTest { private static final String horseKeccak256 = "c87f65ff3f271bf5dc8643484f66b200109caffe4bf98c4cb393dc35740b28c0"; + private static final String inputBlake2bf = + "000000016a09e667f2bd8948bb67ae8584caa73b3c6ef372fe94f82ba54ff53a5f1d36f1510e527fade682d19b05688c2b3e6c1f1f83d9abfb41bd6b5be0cd19137e217907060504030201000f0e0d0c0b0a090817161514131211101f1e1d1c1b1a191827262524232221202f2e2d2c2b2a292837363534333231303f3e3d3c3b3a3938000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000001"; + + private static final String outputBlake2bf = + "62305ad4d48dade8269ef60a1bcb8b7bef6e479e643b5ac1f8017e6422ce89fb62d09ecaa81d095e855540dcbc07bd0feb3d4f5e5e50541260ed930f027cfd8d"; + /** Validate keccak256 hash. */ @Test public void keccak256Hash() { @@ -35,4 +42,11 @@ public void keccak256Hash() { final BytesValue resultCow = Hash.keccak256(BytesValue.wrap("cow".getBytes(UTF_8))); assertEquals(BytesValue.fromHexString(cowKeccak256), resultCow); } + + /** Validate blake2f compression digest. */ + @Test + public void blake2bfCompression() { + final BytesValue result = Hash.blake2bf(BytesValue.wrap(Hex.decode(inputBlake2bf))); + assertEquals(BytesValue.fromHexString(outputBlake2bf), result); + } } diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/Address.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/Address.java index cdff337d75..5ee21a05b2 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/Address.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/Address.java @@ -37,6 +37,7 @@ public class Address extends DelegatingBytesValue { public static final Address ALTBN128_ADD = Address.precompiled(6); public static final Address ALTBN128_MUL = Address.precompiled(7); public static final Address ALTBN128_PAIRING = Address.precompiled(8); + public static final Address BLAKE2B_F_COMPRESSION = Address.precompiled(9); // Last address that can be generated for a pre-compiled contract public static final Integer PRIVACY = Byte.MAX_VALUE - 1; diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/DirectAcyclicGraphSeed.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/DirectAcyclicGraphSeed.java index f205fed40f..959816e43a 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/DirectAcyclicGraphSeed.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/DirectAcyclicGraphSeed.java @@ -14,8 +14,8 @@ import static tech.pegasys.pantheon.ethereum.mainnet.EthHash.EPOCH_LENGTH; -import tech.pegasys.pantheon.crypto.BouncyCastleMessageDigestFactory; import tech.pegasys.pantheon.crypto.Hash; +import tech.pegasys.pantheon.crypto.MessageDigestFactory; import java.security.DigestException; import java.security.MessageDigest; @@ -27,7 +27,7 @@ public class DirectAcyclicGraphSeed { ThreadLocal.withInitial( () -> { try { - return BouncyCastleMessageDigestFactory.create(Hash.KECCAK256_ALG); + return MessageDigestFactory.create(Hash.KECCAK256_ALG); } catch (final NoSuchAlgorithmException ex) { throw new IllegalStateException(ex); } diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetPrecompiledContractRegistries.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetPrecompiledContractRegistries.java index 98f3c0e44e..484c9ec645 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetPrecompiledContractRegistries.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetPrecompiledContractRegistries.java @@ -17,6 +17,7 @@ import tech.pegasys.pantheon.ethereum.mainnet.precompiles.AltBN128AddPrecompiledContract; import tech.pegasys.pantheon.ethereum.mainnet.precompiles.AltBN128MulPrecompiledContract; import tech.pegasys.pantheon.ethereum.mainnet.precompiles.AltBN128PairingPrecompiledContract; +import tech.pegasys.pantheon.ethereum.mainnet.precompiles.BLAKE2BFPrecompileContract; import tech.pegasys.pantheon.ethereum.mainnet.precompiles.BigIntegerModularExponentiationPrecompiledContract; import tech.pegasys.pantheon.ethereum.mainnet.precompiles.ECRECPrecompiledContract; import tech.pegasys.pantheon.ethereum.mainnet.precompiles.IDPrecompiledContract; @@ -100,6 +101,10 @@ public static PrecompileContractRegistry istanbul( Account.DEFAULT_VERSION, AltBN128PairingPrecompiledContract.istanbul( precompiledContractConfiguration.getGasCalculator())); + registry.put( + Address.BLAKE2B_F_COMPRESSION, + Account.DEFAULT_VERSION, + new BLAKE2BFPrecompileContract(precompiledContractConfiguration.getGasCalculator())); return registry; } diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/precompiles/BLAKE2BFPrecompileContract.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/precompiles/BLAKE2BFPrecompileContract.java new file mode 100644 index 0000000000..51bd0037e8 --- /dev/null +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/precompiles/BLAKE2BFPrecompileContract.java @@ -0,0 +1,53 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * Licensed 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 tech.pegasys.pantheon.ethereum.mainnet.precompiles; + +import static java.util.Arrays.copyOfRange; +import static tech.pegasys.pantheon.crypto.Blake2bfMessageDigest.Blake2bfDigest.MESSAGE_LENGTH_BYTES; + +import tech.pegasys.pantheon.crypto.Hash; +import tech.pegasys.pantheon.ethereum.core.Gas; +import tech.pegasys.pantheon.ethereum.mainnet.AbstractPrecompiledContract; +import tech.pegasys.pantheon.ethereum.vm.GasCalculator; +import tech.pegasys.pantheon.ethereum.vm.MessageFrame; +import tech.pegasys.pantheon.util.bytes.BytesValue; + +import java.math.BigInteger; + +// https://github.com/keep-network/go-ethereum/pull/4 +public class BLAKE2BFPrecompileContract extends AbstractPrecompiledContract { + public BLAKE2BFPrecompileContract(final GasCalculator gasCalculator) { + super("BLAKE2f", gasCalculator); + } + + @Override + public Gas gasRequirement(final BytesValue input) { + if (input.size() != MESSAGE_LENGTH_BYTES) { + // Input is malformed, we can't read the number of rounds. + // Precompile can't be executed so we set its price to 0. + return Gas.ZERO; + } + + byte[] roundsBytes = copyOfRange(input.extractArray(), 0, 4); + BigInteger rounds = new BigInteger(1, roundsBytes); + return Gas.of(rounds); + } + + @Override + public BytesValue compute(final BytesValue input, final MessageFrame messageFrame) { + if (input.size() != MESSAGE_LENGTH_BYTES) { + return BytesValue.EMPTY; + } + return Hash.blake2bf(input); + } +} diff --git a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/mainnet/precompiles/BLAKE2BFPrecompileContractTest.java b/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/mainnet/precompiles/BLAKE2BFPrecompileContractTest.java new file mode 100644 index 0000000000..759dfa493b --- /dev/null +++ b/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/mainnet/precompiles/BLAKE2BFPrecompileContractTest.java @@ -0,0 +1,160 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * Licensed 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 tech.pegasys.pantheon.ethereum.mainnet.precompiles; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +import tech.pegasys.pantheon.ethereum.core.Gas; +import tech.pegasys.pantheon.ethereum.mainnet.ConstantinopleFixGasCalculator; +import tech.pegasys.pantheon.ethereum.vm.MessageFrame; +import tech.pegasys.pantheon.util.bytes.BytesValue; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class BLAKE2BFPrecompileContractTest { + private final BLAKE2BFPrecompileContract contract = + new BLAKE2BFPrecompileContract(new ConstantinopleFixGasCalculator()); + + public BLAKE2BFPrecompileContractTest() {} + + private MessageFrame messageFrame = mock(MessageFrame.class); + + // Test vectors from + // https://github.com/keep-network/go-ethereum/blob/f-precompile/core/vm/contracts_test.go#L350 + @Parameterized.Parameters + public static Object[][] parameters() { + return new Object[][] { + { + "000000016a09e667f2bd8948bb67ae8584caa73b3c6ef372fe94f82ba54ff53a5f1d36f1510e527fade682d19b05688c2b3e6c1f1f83d9abfb41bd6b5be0cd19137e217907060504030201000f0e0d0c0b0a090817161514131211101f1e1d1c1b1a191827262524232221202f2e2d2c2b2a292837363534333231303f3e3d3c3b3a3938000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000001", + "62305ad4d48dade8269ef60a1bcb8b7bef6e479e643b5ac1f8017e6422ce89fb62d09ecaa81d095e855540dcbc07bd0feb3d4f5e5e50541260ed930f027cfd8d", + 1 + }, + { + "000000016a09e667f2bd8948bb67ae8584caa73b3c6ef372fe94f82ba54ff53a5f1d36f1510e527fade682d19b05688c2b3e6c1f1f83d9abfb41bd6b5be0cd19137e217907060504030201000f0e0d0c0b0a090817161514131211101f1e1d1c1b1a191827262524232221202f2e2d2c2b2a292837363534333231303f3e3d3c3b3a3938000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000", + "74aaa2510b5c66ea8a708bdea257192d9c9bc48d53c7f7e87a51bffd24d5d9e6ea0b4b2070fb3a3d211208e3f7bcb1a24dec971cecbb62faf1cd142745f8f4ee", + 1 + }, + { + "000000018736a85f01b0a31dd67dcbe79b220b9b9d93e5897bb8975766b47652443b1927b715da2203c2ca1d0d22b820a965ad9cf23bb0bd57a16fd56ed7a9c5dc67af0587868584838281808f8e8d8c8b8a898897969594939291909f9e9d9c9b9a9998a7a6a5a4a3a2a1a0afaeadacabaaa9a8b7b6b5b4b3b2b1b0bfbebdbcbbbab9b8c7c6c5c4c3c2c1c0cfcecdcccbcac9c8d7d6d5d4d3d2d1d0dfdedddcdbdad9d8e7e6e5e4e3e2e1e0efeeedecebeae9e8f7f6f5f4f3f2f1f000fefdfcfbfaf9f8000000000000017f000000000000000001", + "f0eea67995b5e71bd02c40edf19aadd131475949119813de4ea3072f8865bb1978037a72d2bb446d3deead8310b5fba548088c5a99689c51c6632f660a2a2a45", + 1 + }, + { + "000000056a09e667f2bd8948bb67ae8584caa73b3c6ef372fe94f82ba54ff53a5f1d36f1510e527fade682d19b05688c2b3e6c1f1f83d9abfb41bd6b5be0cd19137e217907060504030201000f0e0d0c0b0a090817161514131211101f1e1d1c1b1a191827262524232221202f2e2d2c2b2a292837363534333231303f3e3d3c3b3a3938000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000001", + "f863686fe221e37fee57b5eb7a41266fe765d26d337fa9fe329af1ae378bae9bd61f06dd8ccceec229349cd341b19df399488a9ab05aec7ba3442268fd039ba4", + 5 + }, + { + "000000056a09e667f2bd8948bb67ae8584caa73b3c6ef372fe94f82ba54ff53a5f1d36f1510e527fade682d19b05688c2b3e6c1f1f83d9abfb41bd6b5be0cd19137e217907060504030201000f0e0d0c0b0a090817161514131211101f1e1d1c1b1a191827262524232221202f2e2d2c2b2a292837363534333231303f3e3d3c3b3a3938000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000", + "3a6c5ac8d6606290708c08540cb5c425f72014c8b50c280b817a6ffb1b8b405f2001caca125182b562b8a407a249ca6e9755b62e4324caaf4578a660d8dd4876", + 5 + }, + { + "000000052a3c65f1dbd604cbc713d5ce8c0e5e6c3b22c76bdf73716e55525ba247a930f9e240763f1c01d22c514aa14d3f18722715405fbe7ecde36c77e565078839b7c987868584838281808f8e8d8c8b8a898897969594939291909f9e9d9c9b9a9998a7a6a5a4a3a2a1a0afaeadacabaaa9a8b7b6b5b4b3b2b1b0bfbebdbcbbbab9b8c7c6c5c4c3c2c1c0cfcecdcccbcac9c8d7d6d5d4d3d2d1d0dfdedddcdbdad9d8e7e6e5e4e3e2e1e0efeeedecebeae9e8f7f6f5f4f3f2f1f000fefdfcfbfaf9f8000000000000017f000000000000000001", + "7e7acc657731d1dfb4229114f545597b7fa4b3140217b470e86a8c27ab0b1ce8c103e219a3a2f77fa495b8c706b8016facf32b1cb5d83f2ae8d3248892d5d74c", + 5 + }, + { + "0000000cff3e48ac606d27242b6d0f03be548f10ff2d7f8bccfba4b872deac9005e263ebe6d620e38bba2bf133164736804d08f5dbf004f0c01f6df4bc02b6384c348cde00000000030201000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084000000000000000001", + "7180f3083d5aaabe0569cd951d62cf431dc9f9ff9eb4d014a5ef0eec4192b524ba8b0407d49601f648b0bc8e8246218d6d4fbb56fd42888dacb8aa4d4b9ce1f8", + 12 + }, + { + "0000000cff3e48ac606d27242b6d0f03be548f10ff2d7f8bccfba4b872deac9005e263ebe6d620e38bba2bf133164736804d08f5dbf004f0c01f6df4bc02b6384c348cde07060504030201000f0e0d0c0b0a0908000000141312111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000095000000000000000001", + "099cb773e2cc379a8876af0e51773691d22f53d314339be845292a02de394c76ddf87a51130d71b51cec3be7246631c03620302852f17de6dd18d2b40cab30f3", + 12 + }, + { + "0000000c6a09e667f2bd8948bb67ae8584caa73b3c6ef372fe94f82ba54ff53a5f1d36f1510e527fade682d19b05688c2b3e6c1f1f83d9abfb41bd6b5be0cd19137e217907060504030201000f0e0d0c0b0a090817161514131211101f1e1d1c1b1a191827262524232221202f2e2d2c2b2a292837363534333231303f3e3d3c3b3a3938000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000", + "ff3e48ac606d27242b6d0f03be548f10ff2d7f8bccfba4b872deac9005e263ebe6d620e38bba2bf133164736804d08f5dbf004f0c01f6df4bc02b6384c348cde", + 12 + }, + { + "0000000cff3e48ac606d27242b6d0f03be548f10ff2d7f8bccfba4b872deac9005e263ebe6d620e38bba2bf133164736804d08f5dbf004f0c01f6df4bc02b6384c348cde07060504030201000f0e0d0c0b0a0908171615141312111000000000001a1918000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009b000000000000000001", + "54777d6e31afa286ac6453272e941b20d7d85bab6289ea12f9c8fffbc56d27fb67df67484eae8ca22709162425b7d980b5e078605bda55c81dcab91ce391aa54", + 12 + }, + { + "0000000cff3e48ac606d27242b6d0f03be548f10ff2d7f8bccfba4b872deac9005e263ebe6d620e38bba2bf133164736804d08f5dbf004f0c01f6df4bc02b6384c348cde07060504030201000f0e0d0c0b0a09081716151413121110000000001b1a1918000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009c000000000000000001", + "052780a0caf0bd10793f8aaf9b3606e7a77506a8030a2cd764515ea4e30bb0bb6d6fb5ef88eed1246577e2e65a5477578930fc93e4f5a8c355eedfa133896315", + 12 + }, + { + "0000000cff3e48ac606d27242b6d0f03be548f10ff2d7f8bccfba4b872deac9005e263ebe6d620e38bba2bf133164736804d08f5dbf004f0c01f6df4bc02b6384c348cde07060504030201000f0e0d0c0b0a090817161514131211101f1e1d1c1b1a191827262524232221202f2e2d2c2b2a2928000000000000313000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b2000000000000000001", + "fe47a4b9e2a1e7d01080ffe977223ee8aa7afa12ae75f3c26aa2687831a6a58c32cfc1fb690b7a3601630637eb345da575ba0e2310213d6f7cf5ac546fa52840", + 12 + }, + { + "0000000cff3e48ac606d27242b6d0f03be548f10ff2d7f8bccfba4b872deac9005e263ebe6d620e38bba2bf133164736804d08f5dbf004f0c01f6df4bc02b6384c348cde07060504030201000f0e0d0c0b0a090817161514131211101f1e1d1c1b1a191827262524232221202f2e2d2c2b2a292837363534333231303f3e3d3c3b3a393847464544434241404f4e4d4c4b4a494857565554535251505f5e5d5c5b5a595867666564636261606f6e6d6c6b6a696877767574737271707f7e7d7c7b7a79780000000000000100000000000000000000", + "b02406865b80cde9fec1e321559891be456158a6d71cf0eac1eb845c70d4a3d8948e82cc975dcbd6984780bab7e236382fb585df5bc4b043cb3e866544413d92", + 12 + }, + { + "0000000cb02406865b80cde9fec1e321559891be456158a6d71cf0eac1eb845c70d4a3d8948e82cc975dcbd6984780bab7e236382fb585df5bc4b043cb3e866544413d9287868584838281808f8e8d8c8b8a898897969594939291909f9e9d9c9b9a9998a7a6a5a4a3a2a1a0afaeadacabaaa9a8b7b6b5b4b3b2b1b0bfbebdbcbbbab9b8c7c6c5c4c3c2c1c0cfcecdcccbcac9c8d7d6d5d4d3d2d1d0dfdedddcdbdad9d8e7e6e5e4e3e2e1e000000000000000e8000000000000000000000000000000000000000000000169000000000000000001", + "ca769859d42c76d4db449924feb8b27536b9da1f74ce7ad2eb0f4625e4c6cb160e1838ccade7d451567f4a02897cc47feae4fd8d87db1a19fe0e61a2f52322d6", + 12 + }, + { + "0000000cb02406865b80cde9fec1e321559891be456158a6d71cf0eac1eb845c70d4a3d8948e82cc975dcbd6984780bab7e236382fb585df5bc4b043cb3e866544413d9287868584838281808f8e8d8c8b8a898897969594939291909f9e9d9c9b9a9998a7a6a5a4a3a2a1a0afaeadacabaaa9a8b7b6b5b4b3b2b1b0bfbebdbcbbbab9b8c7c6c5c4c3c2c1c0cfcecdcccbcac9c8d7d6d5d4d3d2d1d0dfdedddcdbdad9d8e7e6e5e4e3e2e1e0efeeedecebeae9e800000000f3f2f1f000000000000000000000000000000174000000000000000001", + "8187dd8f261496b3d5b489bffe2c5e5134e626c210ab2b406c0dfb00e09a6b4eea800ec83e2fcb79168969f8d28019eb51653672749f2ebd37a823cad39c6416", + 12 + }, + { + "0000000cb02406865b80cde9fec1e321559891be456158a6d71cf0eac1eb845c70d4a3d8948e82cc975dcbd6984780bab7e236382fb585df5bc4b043cb3e866544413d9287868584838281808f8e8d8c8b8a898897969594939291909f9e9d9c9b9a9998a7a6a5a4a3a2a1a0afaeadacabaaa9a8b7b6b5b4b3b2b1b0bfbebdbcbbbab9b8c7c6c5c4c3c2c1c0cfcecdcccbcac9c8d7d6d5d4d3d2d1d0dfdedddcdbdad9d8e7e6e5e4e3e2e1e0efeeedecebeae9e8f7f6f5f4f3f2f1f000000000000000f80000000000000179000000000000000001", + "f5b5dbc5e76321b3ef75c8ead211dc1f7e0a0999767ecbbb5daf9507d5a8f87fcdf83e5498ffd974872785043dc19af8567417c800efe05637758ee39fd5e161", + 12 + }, + { + "0000000cff3e48ac606d27242b6d0f03be548f10ff2d7f8bccfba4b872deac9005e263ebe6d620e38bba2bf133164736804d08f5dbf004f0c01f6df4bc02b6384c348cde07060504030201000f0e0d0c0b0a090817161514131211101f1e1d1c1b1a191827262524232221202f2e2d2c2b2a292837363534333231303f3e3d3c3b3a393847464544434241404f4e4d4c4b4a494857565554535251505f5e5d5c5b5a595867666564636261606f6e6d6c6b6a696877767574737271707f7e7d7c7b7a79780000000000000100000000000000000000", + "b02406865b80cde9fec1e321559891be456158a6d71cf0eac1eb845c70d4a3d8948e82cc975dcbd6984780bab7e236382fb585df5bc4b043cb3e866544413d92", + 12 + }, + { + "0000000cb02406865b80cde9fec1e321559891be456158a6d71cf0eac1eb845c70d4a3d8948e82cc975dcbd6984780bab7e236382fb585df5bc4b043cb3e866544413d9287868584838281808f8e8d8c8b8a898897969594939291909f9e9d9c9b9a9998a7a6a5a4a3a2a1a0afaeadacabaaa9a8b7b6b5b4b3b2b1b0bfbebdbcbbbab9b8c7c6c5c4c3c2c1c0cfcecdcccbcac9c8d7d6d5d4d3d2d1d0dfdedddcdbdad9d8e7e6e5e4e3e2e1e0efeeedecebeae9e8f7f6f5f4f3f2f1f000fefdfcfbfaf9f8000000000000017f000000000000000001", + "ccfc282ed60927145b46f8d0fa97afd07010c51d20821e9748923ea42a37a0fa0609a13be7c1e14b6e10a4b63d85d1d56d3d370d80f97b0a61a4f22ed6462dee", + 12 + }, + { + "0000000cb02406865b80cde9fec1e321559891be456158a6d71cf0eac1eb845c70d4a3d8948e82cc975dcbd6984780bab7e236382fb585df5bc4b043cb3e866544413d9287868584838281808f8e8d8c8b8a898897969594939291909f9e9d9c9b9a9998a7a6a5a4a3a2a1a0afaeadacabaaa9a8b7b6b5b4b3b2b1b0bfbebdbcbbbab9b8c7c6c5c4c3c2c1c0cfcecdcccbcac9c8d7d6d5d4d3d2d1d0dfdedddcdbdad9d8e7e6e5e4e3e2e1e0efeeedecebeae9e8f7f6f5f4f3f2f1f000fefdfcfbfaf9f8000000000000017f000000000000000010", + "ccfc282ed60927145b46f8d0fa97afd07010c51d20821e9748923ea42a37a0fa0609a13be7c1e14b6e10a4b63d85d1d56d3d370d80f97b0a61a4f22ed6462dee", + 12 + }, + { + "0000000cb02406865b80cde9fec1e321559891be456158a6d71cf0eac1eb845c70d4a3d8948e82cc975dcbd6984780bab7e236382fb585df5bc4b043cb3e866544413d9287868584838281808f8e8d8c8b8a898897969594939291909f9e9d9c9b9a9998a7a6a5a4a3a2a1a0afaeadacabaaa9a8b7b6b5b4b3b2b1b0bfbebdbcbbbab9b8c7c6c5c4c3c2c1c0cfcecdcccbcac9c8d7d6d5d4d3d2d1d0dfdedddcdbdad9d8e7e6e5e4e3e2e1e0efeeedecebeae9e8f7f6f5f4f3f2f1f000fefdfcfbfaf9f8000000000000017f000000000000000011", + "ccfc282ed60927145b46f8d0fa97afd07010c51d20821e9748923ea42a37a0fa0609a13be7c1e14b6e10a4b63d85d1d56d3d370d80f97b0a61a4f22ed6462dee", + 12 + }, + }; + } + + @Parameterized.Parameter public String input; + + @Parameterized.Parameter(1) + public String expectedResult; + + @Parameterized.Parameter(2) + public long expectedGasUsed; + + @Test + public void shouldRunFCompression() { + final BytesValue input = BytesValue.fromHexString(this.input); + final BytesValue expectedComputation = + expectedResult == null ? BytesValue.EMPTY : BytesValue.fromHexString(expectedResult); + assertThat(contract.compute(input, messageFrame)).isEqualTo(expectedComputation); + assertThat(contract.gasRequirement(input)).isEqualTo(Gas.of(expectedGasUsed)); + } +}