|
| 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 | +} |
0 commit comments