Skip to content

Commit c84b428

Browse files
committed
Buildin keccak
1 parent ac5b7af commit c84b428

File tree

7 files changed

+274
-4
lines changed

7 files changed

+274
-4
lines changed

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ buildscript {
1010
ext.bouncycastleVersion = '1.69'
1111
ext.ed25519Version = '0.3.0'
1212
ext.curve25519Version = '0.5.0'
13-
ext.keccakVersion = '1.1.3'
13+
ext.bignumVersion = '0.3.8'
1414
ext.ktlintVersion = '0.45.1'
1515
repositories {
1616
google()

library/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ dependencies {
2424
api "com.squareup.retrofit2:retrofit:$retrofitVersion"
2525
implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion"
2626
implementation "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:${coroutineAdapterVersion}"
27-
implementation "com.github.komputing.khash:keccak-jvm:${keccakVersion}"
27+
implementation "com.ionspin.kotlin:bignum:${bignumVersion}"
2828
implementation "com.github.mixinnetwork:tink-eddsa:0.0.13"
2929
implementation "com.github.mixinnetwork.jjwt:jjwt-api:2b1c61aa2f"
3030
runtimeOnly 'com.github.mixinnetwork.jjwt:jjwt-impl:2b1c61aa2f'

library/src/main/kotlin/one/mixin/bot/util/CryptoUtil.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ import okio.ByteString.Companion.toByteString
66
import one.mixin.bot.extension.base64Decode
77
import one.mixin.bot.extension.base64Encode
88
import one.mixin.bot.safe.EdKeyPair
9+
import one.mixin.bot.util.keccak.KeccakParameter
10+
import one.mixin.bot.util.keccak.extensions.digestKeccak
911
import one.mixin.eddsa.Ed25519Sign
1012
import one.mixin.eddsa.Field25519
1113
import one.mixin.eddsa.KeyPair.Companion.newKeyPair
1214
import org.bouncycastle.jce.provider.BouncyCastleProvider
13-
import org.komputing.khash.keccak.KeccakParameter
14-
import org.komputing.khash.keccak.extensions.digestKeccak
1515
import org.whispersystems.curve25519.Curve25519
1616
import java.security.KeyFactory
1717
import java.security.KeyPair
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
package one.mixin.bot.util.keccak
2+
3+
import com.ionspin.kotlin.bignum.integer.BigInteger
4+
import one.mixin.bot.util.keccak.extensions.fillWith
5+
import kotlin.math.min
6+
7+
public object Keccak {
8+
private val BIT_65 = BigInteger.ONE shl (64)
9+
private val MAX_64_BITS = BIT_65 - BigInteger.ONE
10+
11+
public fun digest(
12+
value: ByteArray,
13+
parameter: KeccakParameter,
14+
): ByteArray {
15+
val uState = IntArray(200)
16+
val uMessage = convertToUInt(value)
17+
18+
var blockSize = 0
19+
var inputOffset = 0
20+
21+
// Absorbing phase
22+
while (inputOffset < uMessage.size) {
23+
blockSize = min(uMessage.size - inputOffset, parameter.rateInBytes)
24+
for (i in 0 until blockSize) {
25+
uState[i] = uState[i] xor uMessage[i + inputOffset]
26+
}
27+
28+
inputOffset += blockSize
29+
30+
if (blockSize == parameter.rateInBytes) {
31+
doF(uState)
32+
blockSize = 0
33+
}
34+
}
35+
36+
// Padding phase
37+
uState[blockSize] = uState[blockSize] xor parameter.d
38+
if (parameter.d and 0x80 != 0 && blockSize == parameter.rateInBytes - 1) {
39+
doF(uState)
40+
}
41+
42+
uState[parameter.rateInBytes - 1] = uState[parameter.rateInBytes - 1] xor 0x80
43+
doF(uState)
44+
45+
// Squeezing phase
46+
val byteResults = mutableListOf<Byte>()
47+
var tOutputLen = parameter.outputLengthInBytes
48+
while (tOutputLen > 0) {
49+
blockSize = min(tOutputLen, parameter.rateInBytes)
50+
for (i in 0 until blockSize) {
51+
byteResults.add(uState[i].toByte().toInt().toByte())
52+
}
53+
54+
tOutputLen -= blockSize
55+
if (tOutputLen > 0) {
56+
doF(uState)
57+
}
58+
}
59+
60+
return byteResults.toByteArray()
61+
}
62+
63+
private fun doF(uState: IntArray) {
64+
val lState = Array(5) { Array(5) { BigInteger.ZERO } }
65+
66+
for (i in 0..4) {
67+
for (j in 0..4) {
68+
val data = IntArray(8)
69+
val index = 8 * (i + 5 * j)
70+
uState.copyInto(data, 0, index, index + data.size)
71+
lState[i][j] = convertFromLittleEndianTo64(data)
72+
}
73+
}
74+
roundB(lState)
75+
76+
uState.fillWith(0)
77+
for (i in 0..4) {
78+
for (j in 0..4) {
79+
val data = convertFrom64ToLittleEndian(lState[i][j])
80+
data.copyInto(uState, 8 * (i + 5 * j))
81+
}
82+
}
83+
}
84+
85+
/**
86+
* Permutation on the given state.
87+
*/
88+
private fun roundB(state: Array<Array<BigInteger>>) {
89+
var lfsrState = 1
90+
for (round in 0..23) {
91+
val c = arrayOfNulls<BigInteger>(5)
92+
val d = arrayOfNulls<BigInteger>(5)
93+
94+
// θ step
95+
for (i in 0..4) {
96+
c[i] = state[i][0].xor(state[i][1]).xor(state[i][2]).xor(state[i][3]).xor(state[i][4])
97+
}
98+
99+
for (i in 0..4) {
100+
d[i] = c[(i + 4) % 5]!!.xor(c[(i + 1) % 5]!!.leftRotate64(1))
101+
}
102+
103+
for (i in 0..4) {
104+
for (j in 0..4) {
105+
state[i][j] = state[i][j].xor(d[i]!!)
106+
}
107+
}
108+
109+
// ρ and π steps
110+
var x = 1
111+
var y = 0
112+
var current = state[x][y]
113+
for (i in 0..23) {
114+
val tX = x
115+
x = y
116+
y = (2 * tX + 3 * y) % 5
117+
118+
val shiftValue = current
119+
current = state[x][y]
120+
121+
state[x][y] = shiftValue.leftRotate64Safely((i + 1) * (i + 2) / 2)
122+
}
123+
124+
// χ step
125+
for (j in 0..4) {
126+
val t = arrayOfNulls<BigInteger>(5)
127+
for (i in 0..4) {
128+
t[i] = state[i][j]
129+
}
130+
131+
for (i in 0..4) {
132+
// ~t[(i + 1) % 5]
133+
val invertVal = t[(i + 1) % 5]!!.xor(MAX_64_BITS)
134+
// t[i] ^ ((~t[(i + 1) % 5]) & t[(i + 2) % 5])
135+
state[i][j] = t[i]!!.xor(invertVal.and(t[(i + 2) % 5]!!))
136+
}
137+
}
138+
139+
// ι step
140+
for (i in 0..6) {
141+
lfsrState = (lfsrState shl 1 xor (lfsrState shr 7) * 0x71) % 256
142+
// pow(2, i) - 1
143+
val bitPosition = (1 shl i) - 1
144+
if (lfsrState and 2 != 0) {
145+
state[0][0] = state[0][0].xor(BigInteger.ONE shl bitPosition)
146+
}
147+
}
148+
}
149+
}
150+
151+
/**
152+
* Converts the given [data] array to an [IntArray] containing UInt values.
153+
*/
154+
private fun convertToUInt(data: ByteArray) =
155+
IntArray(data.size) {
156+
data[it].toInt() and 0xFF
157+
}
158+
159+
/**
160+
* Converts the given [data] array containing the little endian representation of a number to a [BigInteger].
161+
*/
162+
private fun convertFromLittleEndianTo64(data: IntArray): BigInteger {
163+
val value =
164+
data.map { it.toString(16) }
165+
.map { if (it.length == 2) it else "0$it" }
166+
.reversed()
167+
.joinToString("")
168+
return BigInteger.parseString(value, 16)
169+
}
170+
171+
/**
172+
* Converts the given [BigInteger] to a little endian representation as an [IntArray].
173+
*/
174+
private fun convertFrom64ToLittleEndian(uLong: BigInteger): IntArray {
175+
val asHex = uLong.toString(16)
176+
val asHexPadded = "0".repeat((8 * 2) - asHex.length) + asHex
177+
return IntArray(8) {
178+
((7 - it) * 2).let { pos ->
179+
asHexPadded.substring(pos, pos + 2).toInt(16)
180+
}
181+
}
182+
}
183+
184+
private fun BigInteger.leftRotate64Safely(rotate: Int) = leftRotate64(rotate % 64)
185+
186+
private fun BigInteger.leftRotate64(rotate: Int) = (this shr (64 - rotate)).add(this shl rotate).mod(BIT_65)
187+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package one.mixin.bot.util.keccak
2+
3+
/**
4+
* Parameters defining the FIPS 202 standard.
5+
*/
6+
public enum class KeccakParameter constructor(public val rateInBytes: Int, public val outputLengthInBytes: Int, public val d: Int) {
7+
KECCAK_224(144, 28, 0x01),
8+
KECCAK_256(136, 32, 0x01),
9+
KECCAK_384(104, 48, 0x01),
10+
KECCAK_512(72, 64, 0x01),
11+
12+
SHA3_224(144, 28, 0x06),
13+
SHA3_256(136, 32, 0x06),
14+
SHA3_384(104, 48, 0x06),
15+
SHA3_512(72, 64, 0x06),
16+
17+
SHAKE128(168, 32, 0x1F),
18+
SHAKE256(136, 64, 0x1F),
19+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package one.mixin.bot.util.keccak.extensions
2+
3+
/**
4+
* Assigns the specified int value to each element of the specified
5+
* range of the specified array of ints. The range to be filled
6+
* extends from index <tt>fromIndex</tt>, inclusive, to index
7+
* <tt>toIndex</tt>, exclusive. (If <tt>fromIndex==toIndex</tt>, the
8+
* range to be filled is empty.)
9+
*
10+
* @param fromIndex the index of the first element (inclusive) to be
11+
* filled with the specified value
12+
* @param toIndex the index of the last element (exclusive) to be
13+
* filled with the specified value
14+
* @param value the value to be stored in all elements of the array
15+
* @throws IllegalArgumentException if <tt>fromIndex &gt; toIndex</tt>
16+
* @throws ArrayIndexOutOfBoundsException if <tt>fromIndex &lt; 0</tt> or
17+
* <tt>toIndex &gt; a.length</tt>
18+
*/
19+
internal fun IntArray.fillWith(
20+
value: Int,
21+
fromIndex: Int = 0,
22+
toIndex: Int = this.size,
23+
) {
24+
if (fromIndex > toIndex) {
25+
throw IllegalArgumentException(
26+
"fromIndex($fromIndex) > toIndex($toIndex)",
27+
)
28+
}
29+
30+
if (fromIndex < 0) {
31+
throw ArrayIndexOutOfBoundsException(fromIndex)
32+
}
33+
if (toIndex > this.size) {
34+
throw ArrayIndexOutOfBoundsException(toIndex)
35+
}
36+
37+
for (i in fromIndex until toIndex)
38+
this[i] = value
39+
}
40+
41+
/**
42+
* Constructs a new [ArrayIndexOutOfBoundsException]
43+
* class with an argument indicating the illegal index.
44+
* @param index the illegal index.
45+
*/
46+
internal class ArrayIndexOutOfBoundsException(index: Int) : Throwable("Array index out of range: $index")
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package one.mixin.bot.util.keccak.extensions
2+
3+
import one.mixin.bot.util.keccak.Keccak
4+
import one.mixin.bot.util.keccak.KeccakParameter
5+
6+
/**
7+
* Computes the proper Keccak digest of [this] byte array based on the given [parameter]
8+
*/
9+
public fun ByteArray.digestKeccak(parameter: KeccakParameter): ByteArray {
10+
return Keccak.digest(this, parameter)
11+
}
12+
13+
/**
14+
* Computes the proper Keccak digest of [this] string based on the given [parameter]
15+
*/
16+
public fun String.digestKeccak(parameter: KeccakParameter): ByteArray {
17+
return Keccak.digest(encodeToByteArray(), parameter)
18+
}

0 commit comments

Comments
 (0)