Skip to content

Commit a64605f

Browse files
committed
[ETCM-17] Implement blake2b compress function
1 parent 1fd0a07 commit a64605f

File tree

2 files changed

+186
-0
lines changed

2 files changed

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

0 commit comments

Comments
 (0)