/* * Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved * * You may use, distribute and modify this code under the * terms of the private license, which you must obtain from the author * * To obtain the license, contact the author: https://t.me/real_sergeych or email to * real dot sergeych at gmail. */ package org.komputing.khash.keccak import com.ionspin.kotlin.bignum.integer.BigInteger import org.komputing.khash.keccak.extensions.fillWith import kotlin.math.min @Deprecated("use Hash enum instead, will be removed in next major release", ReplaceWith("Hash", "net.sergeych.crypto2.Hash")) object Keccak { private val BIT_65 = BigInteger.ONE shl (64) private val MAX_64_BITS = BIT_65 - BigInteger.ONE fun digest(value: ByteArray, parameter: KeccakParameter): ByteArray { val uState = IntArray(200) val uMessage = convertToUInt(value) var blockSize = 0 var inputOffset = 0 // Absorbing phase while (inputOffset < uMessage.size) { blockSize = min(uMessage.size - inputOffset, parameter.rateInBytes) for (i in 0 until blockSize) { uState[i] = uState[i] xor uMessage[i + inputOffset] } inputOffset += blockSize if (blockSize == parameter.rateInBytes) { doF(uState) blockSize = 0 } } // Padding phase uState[blockSize] = uState[blockSize] xor parameter.d if (parameter.d and 0x80 != 0 && blockSize == parameter.rateInBytes - 1) { doF(uState) } uState[parameter.rateInBytes - 1] = uState[parameter.rateInBytes - 1] xor 0x80 doF(uState) // Squeezing phase val byteResults = mutableListOf() var tOutputLen = parameter.outputLengthInBytes while (tOutputLen > 0) { blockSize = min(tOutputLen, parameter.rateInBytes) for (i in 0 until blockSize) { byteResults.add(uState[i].toByte().toInt().toByte()) } tOutputLen -= blockSize if (tOutputLen > 0) { doF(uState) } } return byteResults.toByteArray() } private fun doF(uState: IntArray) { val lState = Array(5) { Array(5) { BigInteger.ZERO } } for (i in 0..4) { for (j in 0..4) { val data = IntArray(8) val index = 8 * (i + 5 * j) uState.copyInto(data, 0, index, index + data.size) lState[i][j] = convertFromLittleEndianTo64(data) } } roundB(lState) uState.fillWith(0) for (i in 0..4) { for (j in 0..4) { val data = convertFrom64ToLittleEndian(lState[i][j]) data.copyInto(uState, 8 * (i + 5 * j)) } } } /** * Permutation on the given state. */ private fun roundB(state: Array>) { var lfsrState = 1 for (round in 0..23) { val c = arrayOfNulls(5) val d = arrayOfNulls(5) // θ step for (i in 0..4) { c[i] = state[i][0].xor(state[i][1]).xor(state[i][2]).xor(state[i][3]).xor(state[i][4]) } for (i in 0..4) { d[i] = c[(i + 4) % 5]!!.xor(c[(i + 1) % 5]!!.leftRotate64(1)) } for (i in 0..4) { for (j in 0..4) { state[i][j] = state[i][j].xor(d[i]!!) } } // ρ and π steps var x = 1 var y = 0 var current = state[x][y] for (i in 0..23) { val tX = x x = y y = (2 * tX + 3 * y) % 5 val shiftValue = current current = state[x][y] state[x][y] = shiftValue.leftRotate64Safely((i + 1) * (i + 2) / 2) } // χ step for (j in 0..4) { val t = arrayOfNulls(5) for (i in 0..4) { t[i] = state[i][j] } for (i in 0..4) { // ~t[(i + 1) % 5] val invertVal = t[(i + 1) % 5]!!.xor(MAX_64_BITS) // t[i] ^ ((~t[(i + 1) % 5]) & t[(i + 2) % 5]) state[i][j] = t[i]!!.xor(invertVal.and(t[(i + 2) % 5]!!)) } } // ι step for (i in 0..6) { lfsrState = (lfsrState shl 1 xor (lfsrState shr 7) * 0x71) % 256 // pow(2, i) - 1 val bitPosition = (1 shl i) - 1 if (lfsrState and 2 != 0) { state[0][0] = state[0][0].xor(BigInteger.ONE shl bitPosition) } } } } /** * Converts the given [data] array to an [IntArray] containing UInt values. */ private fun convertToUInt(data: ByteArray) = IntArray(data.size) { data[it].toInt() and 0xFF } /** * Converts the given [data] array containing the little endian representation of a number to a [BigInteger]. */ private fun convertFromLittleEndianTo64(data: IntArray): BigInteger { val value = data.map { it.toString(16) } .map { if (it.length == 2) it else "0$it" } .reversed() .joinToString("") return BigInteger.parseString(value, 16) } /** * Converts the given [BigInteger] to a little endian representation as an [IntArray]. */ private fun convertFrom64ToLittleEndian(uLong: BigInteger): IntArray { val asHex = uLong.toString(16) val asHexPadded = "0".repeat((8 * 2) - asHex.length) + asHex return IntArray(8) { ((7 - it) * 2).let { pos -> asHexPadded.substring(pos, pos + 2).toInt(16) } } } private fun BigInteger.leftRotate64Safely(rotate: Int) = leftRotate64(rotate % 64) private fun BigInteger.leftRotate64(rotate: Int) = (this shr (64 - rotate)).add(this shl rotate).mod(BIT_65) }