Skip to content

Commit 362b99c

Browse files
committed
Merge remote-tracking branch 'origin/etcm-17-blake2-pre-compile' into phase/etc_forks
2 parents f84f4c3 + fe1b320 commit 362b99c

File tree

4 files changed

+227
-2
lines changed

4 files changed

+227
-2
lines changed
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
package io.iohk.ethereum.vm
2+
3+
import java.util.Arrays.copyOfRange
4+
5+
// scalastyle:off magic.number
6+
object Blake2bCompression {
7+
val MessageBytesLength = 213
8+
9+
import org.bouncycastle.util.Pack
10+
11+
private val IV: Array[Long] = Array(
12+
0x6a09e667f3bcc908L,
13+
0xbb67ae8584caa73bL,
14+
0x3c6ef372fe94f82bL,
15+
0xa54ff53a5f1d36f1L,
16+
0x510e527fade682d1L,
17+
0x9b05688c2b3e6c1fL,
18+
0x1f83d9abfb41bd6bL,
19+
0x5be0cd19137e2179L)
20+
21+
private val PRECOMPUTED: Array[Array[Byte]] = Array(
22+
Array(0, 2, 4, 6, 1, 3, 5, 7, 8, 10, 12, 14, 9, 11, 13, 15),
23+
Array(14, 4, 9, 13, 10, 8, 15, 6, 1, 0, 11, 5, 12, 2, 7, 3),
24+
Array(11, 12, 5, 15, 8, 0, 2, 13, 10, 3, 7, 9, 14, 6, 1, 4),
25+
Array(7, 3, 13, 11, 9, 1, 12, 14, 2, 5, 4, 15, 6, 10, 0, 8),
26+
Array(9, 5, 2, 10, 0, 7, 4, 15, 14, 11, 6, 3, 1, 12, 8, 13),
27+
Array(2, 6, 0, 8, 12, 10, 11, 3, 4, 7, 15, 1, 13, 5, 14, 9),
28+
Array(12, 1, 14, 4, 5, 15, 13, 10, 0, 6, 9, 8, 7, 3, 2, 11),
29+
Array(13, 7, 12, 3, 11, 14, 1, 9, 5, 15, 8, 2, 0, 4, 6, 10),
30+
Array(6, 14, 11, 0, 15, 9, 3, 8, 12, 13, 1, 10, 2, 7, 4, 5),
31+
Array(10, 8, 7, 1, 2, 4, 6, 5, 15, 9, 3, 13, 11, 14, 12, 0))
32+
33+
private def bytesToInt(bytes: Array[Byte]) = Pack.bigEndianToInt(bytes, 0)
34+
35+
private def bytesToLong(bytes: Array[Byte]) = Pack.littleEndianToLong(bytes, 0)
36+
37+
def isValidInput(input: Array[Byte]): Boolean =
38+
!(input.length != MessageBytesLength || (input(212) & 0xFE) != 0)
39+
40+
def parseNumberOfRounds(input: Array[Byte]): Long =
41+
Integer.toUnsignedLong(bytesToInt(copyOfRange(input, 0, 4)))
42+
/**
43+
* Parses input according to the rules defined in: https://eips.ethereum.org/EIPS/eip-152
44+
* The encoded inputs are corresponding to the ones specified in the BLAKE2 RFC Section 3.2:
45+
46+
* rounds - the number of rounds - 32-bit unsigned big-endian word
47+
* h - the state vector - 8 unsigned 64-bit little-endian words
48+
* m - the message block vector - 16 unsigned 64-bit little-endian words
49+
* t_0, t_1 - offset counters - 2 unsigned 64-bit little-endian words
50+
* f - the final block indicator flag - 8-bit word
51+
*
52+
* @param input [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]
53+
* @return all parsed inputs from input array: (rounds, h, m, t, f)
54+
*/
55+
private def parseInput(input: Array[Byte]): (Long, Array[Long], Array[Long], Array[Long], Boolean) = {
56+
val rounds = parseNumberOfRounds(input)
57+
val h = new Array[Long](8)
58+
val m = new Array[Long](16)
59+
val t = new Array[Long](2)
60+
61+
var i = 0
62+
while (i < h.length) {
63+
val offset = 4 + i * 8
64+
h(i) = bytesToLong(copyOfRange(input, offset, offset + 8))
65+
i += 1
66+
}
67+
68+
var j = 0
69+
while (j < 16) {
70+
val offset = 68 + j * 8
71+
m(j) = bytesToLong(copyOfRange(input, offset, offset + 8))
72+
j += 1
73+
}
74+
75+
t(0) = bytesToLong(copyOfRange(input, 196, 204))
76+
t(1) = bytesToLong(copyOfRange(input, 204, 212))
77+
val f = input(212) != 0
78+
(rounds, h, m, t, f)
79+
}
80+
81+
def blake2bCompress(input: Array[Byte]): Option[Array[Byte]] = {
82+
if (isValidInput(input)) {
83+
val (rounds, h, m, t, f) = parseInput(input)
84+
compress(rounds, h, m, t, f)
85+
Some(convertToBytes(h))
86+
} else {
87+
None
88+
}
89+
}
90+
91+
private def convertToBytes(h: Array[Long]): Array[Byte] = {
92+
var i = 0
93+
val out = new Array[Byte](h.length * 8)
94+
while (i < h.length) {
95+
System.arraycopy(Pack.longToLittleEndian(h(i)), 0, out, i * 8, 8)
96+
i += 1
97+
}
98+
out
99+
}
100+
101+
private def compress(rounds: Long, h: Array[Long], m: Array[Long], t: Array[Long], f: Boolean): Unit = {
102+
val v = new Array[Long](16)
103+
val t0 = t(0)
104+
val t1 = t(1)
105+
System.arraycopy(h, 0, v, 0, 8)
106+
System.arraycopy(IV, 0, v, 8, 8)
107+
v(12) ^= t0
108+
v(13) ^= t1
109+
110+
if (f) {
111+
v(14) ^= 0xffffffffffffffffL
112+
}
113+
114+
var j = 0L
115+
while (j < rounds) {
116+
val s: Array[Byte] = PRECOMPUTED((j % 10).toInt)
117+
mix(v, m(s(0)), m(s(4)), 0, 4, 8, 12)
118+
mix(v, m(s(1)), m(s(5)), 1, 5, 9, 13)
119+
mix(v, m(s(2)), m(s(6)), 2, 6, 10, 14)
120+
mix(v, m(s(3)), m(s(7)), 3, 7, 11, 15)
121+
mix(v, m(s(8)), m(s(12)), 0, 5, 10, 15)
122+
mix(v, m(s(9)), m(s(13)), 1, 6, 11, 12)
123+
mix(v, m(s(10)), m(s(14)), 2, 7, 8, 13)
124+
mix(v, m(s(11)), m(s(15)), 3, 4, 9, 14)
125+
j += 1
126+
}
127+
128+
// update h:
129+
var offset = 0
130+
while (offset < h.length) {
131+
h(offset) ^= v(offset) ^ v(offset + 8)
132+
offset += 1
133+
}
134+
}
135+
136+
private def mix(v: Array[Long], a: Long, b: Long, i: Int, j: Int, k: Int, l: Int): Unit = {
137+
v(i) += a + v(j)
138+
v(l) = java.lang.Long.rotateLeft(v(l) ^ v(i), -32)
139+
v(k) += v(l)
140+
v(j) = java.lang.Long.rotateLeft(v(j) ^ v(k), -24)
141+
v(i) += b + v(j)
142+
v(l) = java.lang.Long.rotateLeft(v(l) ^ v(i), -16)
143+
v(k) += v(l)
144+
v(j) = java.lang.Long.rotateLeft(v(j) ^ v(k), -63)
145+
}
146+
}

src/main/scala/io/iohk/ethereum/vm/PrecompiledContracts.scala

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import io.iohk.ethereum.utils.ByteUtils
1010
import io.iohk.ethereum.vm.BlockchainConfigForEvm.EtcForks.EtcFork
1111
import io.iohk.ethereum.vm.BlockchainConfigForEvm.EthForks.EthFork
1212
import io.iohk.ethereum.vm.BlockchainConfigForEvm.{EtcForks, EthForks}
13+
1314
import scala.util.Try
1415

1516
// scalastyle:off magic.number
@@ -23,6 +24,7 @@ object PrecompiledContracts {
2324
val Bn128AddAddr = Address(6)
2425
val Bn128MulAddr = Address(7)
2526
val Bn128PairingAddr = Address(8)
27+
val Blake2bCompressionAddr = Address(9)
2628

2729
val contracts = Map(
2830
EcDsaRecAddr -> EllipticCurveRecovery,
@@ -37,6 +39,10 @@ object PrecompiledContracts {
3739
Bn128MulAddr -> Bn128Mul,
3840
Bn128PairingAddr -> Bn128Pairing
3941
)
42+
43+
val istanbulPhoenixContracts = byzantiumAtlantisContracts ++ Map(
44+
Blake2bCompressionAddr -> Blake2bCompress
45+
)
4046
/**
4147
* Checks whether `ProgramContext#recipientAddr` points to a precompiled contract
4248
*/
@@ -53,8 +59,12 @@ object PrecompiledContracts {
5359

5460
private def getContract(context: ProgramContext[_, _]): Option[PrecompiledContract] = {
5561
context.recipientAddr.flatMap{ addr =>
56-
if (context.evmConfig.blockchainConfig.ethForkForBlockNumber(context.blockHeader.number) >= EthForks.Byzantium ||
57-
context.evmConfig.blockchainConfig.etcForkForBlockNumber(context.blockHeader.number) >= EtcForks.Atlantis) {
62+
val ethFork = context.evmConfig.blockchainConfig.ethForkForBlockNumber(context.blockHeader.number)
63+
val etcFork = context.evmConfig.blockchainConfig.etcForkForBlockNumber(context.blockHeader.number)
64+
65+
if (ethFork >= EthForks.Istanbul || etcFork >= EtcForks.Phoenix) {
66+
istanbulPhoenixContracts.get(addr)
67+
} else if (ethFork >= EthForks.Byzantium || etcFork >= EtcForks.Atlantis) {
5868
// byzantium and atlantis hard fork introduce the same set of precompiled contracts
5969
byzantiumAtlantisContracts.get(addr)
6070
} else
@@ -375,4 +385,23 @@ object PrecompiledContracts {
375385
input.slice(from, from + wordLength)
376386
}
377387
}
388+
389+
//Spec: https://eips.ethereum.org/EIPS/eip-152
390+
// scalastyle: off
391+
object Blake2bCompress extends PrecompiledContract {
392+
def exec(inputData: ByteString): Option[ByteString] = {
393+
Blake2bCompression.blake2bCompress(inputData.toArray).map(ByteString.fromArrayUnsafe)
394+
}
395+
396+
def gas(inputData: ByteString, etcFork: EtcFork, ethFork: EthFork): BigInt = {
397+
val inputArray = inputData.toArray
398+
if (Blake2bCompression.isValidInput(inputArray)) {
399+
// Each round costs 1gas
400+
Blake2bCompression.parseNumberOfRounds(inputArray)
401+
} else {
402+
// bad input to contract, contract will not execute, set price to zero
403+
0
404+
}
405+
}
406+
}
378407
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package io.iohk.ethereum.vm
2+
3+
import org.bouncycastle.util.encoders.Hex
4+
import org.scalatest.prop.PropertyChecks
5+
import org.scalatest.{FlatSpec, Matchers}
6+
7+
class BlakeCompressionSpec extends FlatSpec with Matchers with PropertyChecks {
8+
// test vectors from: https://eips.ethereum.org/EIPS/eip-152
9+
val testVectors = Table[String, Option[String]](
10+
("value", "result"),
11+
("00000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", None),
12+
("000000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", None),
13+
("0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000002", None),
14+
("0000000048c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", Some("08c9bcf367e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d282e6ad7f520e511f6c3e2b8c68059b9442be0454267ce079217e1319cde05b")),
15+
("0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", Some("ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923")),
16+
("0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000", Some("75ab69d3190a562c51aef8d88f1c2775876944407270c42c9844252c26d2875298743e7f6d5ea2f2d3e8d226039cd31b4e426ac4f2d3d666a610c2116fde4735")),
17+
("0000000148c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", Some("b63a380cb2897d521994a85234ee2c181b5f844d2c624c002677e9703449d2fba551b3a8333bcdf5f2f7e08993d53923de3d64fcc68c034e717b9293fed7a421"))
18+
)
19+
20+
"Blake2b compression function" should "handle all test vectors" in {
21+
forAll(testVectors) { (value, expectedResult) =>
22+
val asBytes = Hex.decode(value)
23+
val result = Blake2bCompression.blake2bCompress(asBytes)
24+
val resultAsString = result.map(str => Hex.toHexString(str))
25+
assert(resultAsString == expectedResult)
26+
}
27+
}
28+
29+
it should "handle empty input" in {
30+
val result = Blake2bCompression.blake2bCompress(Array())
31+
assert(result.isEmpty)
32+
}
33+
}

src/test/scala/io/iohk/ethereum/vm/PrecompiledContractsSpec.scala

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,4 +248,21 @@ class PrecompiledContractsSpec extends FunSuite with Matchers with PropertyCheck
248248
result.returnData shouldEqual ByteString(Hex.decode(expectedResult))
249249
}
250250
}
251+
252+
test("BLAKE2bCompress") {
253+
val testData = Table(("input", "Expected"),
254+
("0000000148c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", "b63a380cb2897d521994a85234ee2c181b5f844d2c624c002677e9703449d2fba551b3a8333bcdf5f2f7e08993d53923de3d64fcc68c034e717b9293fed7a421")
255+
)
256+
257+
forAll(testData) { (input, expectedResult) =>
258+
val inputArray = Hex.decode(input)
259+
val expectedNumOfRounds = BigInt(1, inputArray.take(4))
260+
val context = buildContext(PrecompiledContracts.Blake2bCompressionAddr, ByteString(inputArray))
261+
val result = vm.run(context)
262+
val gasUsed = context.startGas - result.gasRemaining
263+
gasUsed shouldEqual expectedNumOfRounds
264+
result.returnData shouldEqual ByteString(Hex.decode(expectedResult))
265+
}
266+
}
267+
251268
}

0 commit comments

Comments
 (0)