Skip to content

Commit 28789a9

Browse files
committed
[ETCM-17] Implement blake2b compress precompiled contract
1 parent 8ea4dae commit 28789a9

File tree

3 files changed

+52
-7
lines changed

3 files changed

+52
-7
lines changed

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ object Blake2bCompression {
4444

4545
def parseNumberOfRounds(input: Array[Byte]): Long =
4646
Integer.toUnsignedLong(bytesToInt(copyOfRange(input, 0, 4)))
47-
4847
/**
4948
* Parses input according to the rules defined in: https://eips.ethereum.org/EIPS/eip-152
5049
* The encoded inputs are corresponding to the ones specified in the BLAKE2 RFC Section 3.2:
@@ -58,8 +57,8 @@ object Blake2bCompression {
5857
* @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]
5958
* @return all parsed inputs from input array: (rounds, h, m, t, f)
6059
*/
61-
def parseInput(input: Array[Byte]): (Long, Array[Long], Array[Long], Array[Long], Boolean) = {
62-
val rounds =parseNumberOfRounds(input)
60+
private def parseInput(input: Array[Byte]): (Long, Array[Long], Array[Long], Array[Long], Boolean) = {
61+
val rounds = parseNumberOfRounds(input)
6362
val h = new Array[Long](8)
6463
val m = new Array[Long](16)
6564
val t = new Array[Long](2)
@@ -88,13 +87,13 @@ object Blake2bCompression {
8887
if (isValidInput(input)) {
8988
val (rounds, h, m, t, f) = parseInput(input)
9089
compress(rounds, h, m, t, f)
91-
Some(compute(h))
90+
Some(convertToBytes(h))
9291
} else {
9392
None
9493
}
9594
}
9695

97-
private def compute(h: Array[Long]): Array[Byte] = {
96+
private def convertToBytes(h: Array[Long]): Array[Byte] = {
9897
var i = 0
9998
val out = new Array[Byte](h.length * 8)
10099
while (i < h.length) {

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 istanbulPhoenisContracts = 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+
istanbulPhoenisContracts.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
}

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)