-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
274 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
187 changes: 187 additions & 0 deletions
187
library/src/main/kotlin/one/mixin/bot/util/keccak/Keccak.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
package one.mixin.bot.util.keccak | ||
|
||
import com.ionspin.kotlin.bignum.integer.BigInteger | ||
import one.mixin.bot.util.keccak.extensions.fillWith | ||
import kotlin.math.min | ||
|
||
public object Keccak { | ||
private val BIT_65 = BigInteger.ONE shl (64) | ||
private val MAX_64_BITS = BIT_65 - BigInteger.ONE | ||
|
||
public 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<Byte>() | ||
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<Array<BigInteger>>) { | ||
var lfsrState = 1 | ||
for (round in 0..23) { | ||
val c = arrayOfNulls<BigInteger>(5) | ||
val d = arrayOfNulls<BigInteger>(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<BigInteger>(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) | ||
} |
19 changes: 19 additions & 0 deletions
19
library/src/main/kotlin/one/mixin/bot/util/keccak/KeccakParameter.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package one.mixin.bot.util.keccak | ||
|
||
/** | ||
* Parameters defining the FIPS 202 standard. | ||
*/ | ||
public enum class KeccakParameter constructor(public val rateInBytes: Int, public val outputLengthInBytes: Int, public val d: Int) { | ||
KECCAK_224(144, 28, 0x01), | ||
KECCAK_256(136, 32, 0x01), | ||
KECCAK_384(104, 48, 0x01), | ||
KECCAK_512(72, 64, 0x01), | ||
|
||
SHA3_224(144, 28, 0x06), | ||
SHA3_256(136, 32, 0x06), | ||
SHA3_384(104, 48, 0x06), | ||
SHA3_512(72, 64, 0x06), | ||
|
||
SHAKE128(168, 32, 0x1F), | ||
SHAKE256(136, 64, 0x1F), | ||
} |
46 changes: 46 additions & 0 deletions
46
library/src/main/kotlin/one/mixin/bot/util/keccak/extensions/IntArrayExtensions.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package one.mixin.bot.util.keccak.extensions | ||
|
||
/** | ||
* Assigns the specified int value to each element of the specified | ||
* range of the specified array of ints. The range to be filled | ||
* extends from index <tt>fromIndex</tt>, inclusive, to index | ||
* <tt>toIndex</tt>, exclusive. (If <tt>fromIndex==toIndex</tt>, the | ||
* range to be filled is empty.) | ||
* | ||
* @param fromIndex the index of the first element (inclusive) to be | ||
* filled with the specified value | ||
* @param toIndex the index of the last element (exclusive) to be | ||
* filled with the specified value | ||
* @param value the value to be stored in all elements of the array | ||
* @throws IllegalArgumentException if <tt>fromIndex > toIndex</tt> | ||
* @throws ArrayIndexOutOfBoundsException if <tt>fromIndex < 0</tt> or | ||
* <tt>toIndex > a.length</tt> | ||
*/ | ||
internal fun IntArray.fillWith( | ||
value: Int, | ||
fromIndex: Int = 0, | ||
toIndex: Int = this.size, | ||
) { | ||
if (fromIndex > toIndex) { | ||
throw IllegalArgumentException( | ||
"fromIndex($fromIndex) > toIndex($toIndex)", | ||
) | ||
} | ||
|
||
if (fromIndex < 0) { | ||
throw ArrayIndexOutOfBoundsException(fromIndex) | ||
} | ||
if (toIndex > this.size) { | ||
throw ArrayIndexOutOfBoundsException(toIndex) | ||
} | ||
|
||
for (i in fromIndex until toIndex) | ||
this[i] = value | ||
} | ||
|
||
/** | ||
* Constructs a new [ArrayIndexOutOfBoundsException] | ||
* class with an argument indicating the illegal index. | ||
* @param index the illegal index. | ||
*/ | ||
internal class ArrayIndexOutOfBoundsException(index: Int) : Throwable("Array index out of range: $index") |
18 changes: 18 additions & 0 deletions
18
library/src/main/kotlin/one/mixin/bot/util/keccak/extensions/PublicExtensions.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package one.mixin.bot.util.keccak.extensions | ||
|
||
import one.mixin.bot.util.keccak.Keccak | ||
import one.mixin.bot.util.keccak.KeccakParameter | ||
|
||
/** | ||
* Computes the proper Keccak digest of [this] byte array based on the given [parameter] | ||
*/ | ||
public fun ByteArray.digestKeccak(parameter: KeccakParameter): ByteArray { | ||
return Keccak.digest(this, parameter) | ||
} | ||
|
||
/** | ||
* Computes the proper Keccak digest of [this] string based on the given [parameter] | ||
*/ | ||
public fun String.digestKeccak(parameter: KeccakParameter): ByteArray { | ||
return Keccak.digest(encodeToByteArray(), parameter) | ||
} |