Skip to content

Commit

Permalink
Buildin keccak
Browse files Browse the repository at this point in the history
  • Loading branch information
Tougee committed Dec 19, 2023
1 parent ac5b7af commit c84b428
Show file tree
Hide file tree
Showing 7 changed files with 274 additions and 4 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ buildscript {
ext.bouncycastleVersion = '1.69'
ext.ed25519Version = '0.3.0'
ext.curve25519Version = '0.5.0'
ext.keccakVersion = '1.1.3'
ext.bignumVersion = '0.3.8'
ext.ktlintVersion = '0.45.1'
repositories {
google()
Expand Down
2 changes: 1 addition & 1 deletion library/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ dependencies {
api "com.squareup.retrofit2:retrofit:$retrofitVersion"
implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion"
implementation "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:${coroutineAdapterVersion}"
implementation "com.github.komputing.khash:keccak-jvm:${keccakVersion}"
implementation "com.ionspin.kotlin:bignum:${bignumVersion}"
implementation "com.github.mixinnetwork:tink-eddsa:0.0.13"
implementation "com.github.mixinnetwork.jjwt:jjwt-api:2b1c61aa2f"
runtimeOnly 'com.github.mixinnetwork.jjwt:jjwt-impl:2b1c61aa2f'
Expand Down
4 changes: 2 additions & 2 deletions library/src/main/kotlin/one/mixin/bot/util/CryptoUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import okio.ByteString.Companion.toByteString
import one.mixin.bot.extension.base64Decode
import one.mixin.bot.extension.base64Encode
import one.mixin.bot.safe.EdKeyPair
import one.mixin.bot.util.keccak.KeccakParameter
import one.mixin.bot.util.keccak.extensions.digestKeccak
import one.mixin.eddsa.Ed25519Sign
import one.mixin.eddsa.Field25519
import one.mixin.eddsa.KeyPair.Companion.newKeyPair
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.komputing.khash.keccak.KeccakParameter
import org.komputing.khash.keccak.extensions.digestKeccak
import org.whispersystems.curve25519.Curve25519
import java.security.KeyFactory
import java.security.KeyPair
Expand Down
187 changes: 187 additions & 0 deletions library/src/main/kotlin/one/mixin/bot/util/keccak/Keccak.kt
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)
}
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),
}
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 &gt; toIndex</tt>
* @throws ArrayIndexOutOfBoundsException if <tt>fromIndex &lt; 0</tt> or
* <tt>toIndex &gt; 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")
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)
}

0 comments on commit c84b428

Please sign in to comment.