package com.infendro.hash.keccak import com.infendro.bytes.ByteOrder.LITTLE_ENDIAN import com.infendro.bytes.bytearray.copyInto import com.infendro.bytes.long.copyInto import com.infendro.hash.HashFunction open class Keccak( bytes: Int, private val rate: Int, private val domain: Byte, ) : HashFunction(bytes, rate) { init { require(bytes > 0) require(rate in 8..192 step 8) } private var state = LongArray(25) private val w = LongArray(block / 8) private val b = LongArray(25) private val c = LongArray(5) private val d = LongArray(5) override fun reset() { super.reset() state.fill(0L) } override fun process() { absorb(buffer.value) permute() } private fun finalize() { buffer.append(domain) if (buffer.free < 1) { process() buffer.reset() } buffer.jumpTo(block - 1) buffer.append(0x80.toByte()) process() } override fun digestInto(destination: ByteArray, destinationOffset: Int) { finalize() squeeze(destination, destinationOffset) reset() } private fun absorb(block: ByteArray) { block.copyInto(w, order = LITTLE_ENDIAN) for (i in w.indices) { state[i] = state[i] xor w[i] } } private fun squeeze(destination: ByteArray, destinationOffset: Int) { val buffer = ByteArray(8) val threshold = rate / 8 var consumed = 0 var i = 0 while (consumed < bytes) { if (i == threshold) { permute() i = 0 } val length = minOf(bytes - consumed, 8) state[i].copyInto(buffer, order = LITTLE_ENDIAN) buffer.copyInto(destination, destinationOffset + consumed, endIndex = length) consumed += length i++ } } private fun permute() = repeat(24) { round -> val a = state // θ for (x in 0..4) { c[x] = a[x] xor a[x + 5] xor a[x + 10] xor a[x + 15] xor a[x + 20] } for (x in 0..4) { d[x] = c[(x + 4) % 5] xor c[(x + 1) % 5].rotateLeft(1) } for (x in 0..4) for (y in 0..4) { a[x + 5 * y] = a[x + 5 * y] xor d[x] } // ρ and π for (x in 0..4) for (y in 0..4) { b[y + 5 * ((2 * x + 3 * y) % 5)] = a[x + 5 * y].rotateLeft(rotation[x][y]) } // χ for (x in 0..4) for (y in 0..4) { a[x + 5 * y] = b[x + 5 * y] xor ((b[((x + 1) % 5) + 5 * y].inv()) and b[((x + 2) % 5) + 5 * y]) } // ι a[0] = a[0] xor rc[round] } companion object { private val rc = longArrayOf( +0x0000000000000001, +0x0000000000008082, -0x7fffffffffff7f76, -0x7fffffff7fff8000, +0x000000000000808b, +0x0000000080000001, -0x7fffffff7fff7f7f, -0x7fffffffffff7ff7, +0x000000000000008a, +0x0000000000000088, +0x0000000080008009, +0x000000008000000a, +0x000000008000808b, -0x7fffffffffffff75, -0x7fffffffffff7f77, -0x7fffffffffff7ffd, -0x7fffffffffff7ffe, -0x7fffffffffffff80, +0x000000000000800a, -0x7fffffff7ffffff6, -0x7fffffff7fff7f7f, -0x7fffffffffff7f80, +0x0000000080000001, -0x7fffffff7fff7ff8, ) private val rotation = arrayOf( intArrayOf(0, 36, 3, 41, 18), intArrayOf(1, 44, 10, 45, 2), intArrayOf(62, 6, 43, 15, 61), intArrayOf(28, 55, 25, 21, 56), intArrayOf(27, 20, 39, 8, 14), ) } }