From e3619a2df15b93ed491039097c70d9e49fe3f419 Mon Sep 17 00:00:00 2001 From: Infendro Date: Sun, 9 Nov 2025 20:53:01 +0100 Subject: [PATCH] implement keccak --- .../kotlin/com/infendro/hash/keccak/Keccak.kt | 113 ++++++++++++++++++ .../com/infendro/hash/keccak/sha3/SHA3-224.kt | 6 + .../com/infendro/hash/keccak/sha3/SHA3-256.kt | 6 + .../com/infendro/hash/keccak/sha3/SHA3-384.kt | 6 + .../com/infendro/hash/keccak/sha3/SHA3-512.kt | 6 + .../infendro/hash/keccak/shake/SHAKE128.kt | 6 + .../infendro/hash/keccak/shake/SHAKE256.kt | 6 + 7 files changed, 149 insertions(+) create mode 100644 src/commonMain/kotlin/com/infendro/hash/keccak/Keccak.kt create mode 100644 src/commonMain/kotlin/com/infendro/hash/keccak/sha3/SHA3-224.kt create mode 100644 src/commonMain/kotlin/com/infendro/hash/keccak/sha3/SHA3-256.kt create mode 100644 src/commonMain/kotlin/com/infendro/hash/keccak/sha3/SHA3-384.kt create mode 100644 src/commonMain/kotlin/com/infendro/hash/keccak/sha3/SHA3-512.kt create mode 100644 src/commonMain/kotlin/com/infendro/hash/keccak/shake/SHAKE128.kt create mode 100644 src/commonMain/kotlin/com/infendro/hash/keccak/shake/SHAKE256.kt diff --git a/src/commonMain/kotlin/com/infendro/hash/keccak/Keccak.kt b/src/commonMain/kotlin/com/infendro/hash/keccak/Keccak.kt new file mode 100644 index 0000000..6c9716f --- /dev/null +++ b/src/commonMain/kotlin/com/infendro/hash/keccak/Keccak.kt @@ -0,0 +1,113 @@ +package com.infendro.hash.keccak + +import com.infendro.bytearray.ByteOrder.LITTLE_ENDIAN +import com.infendro.bytearray.addAll +import com.infendro.bytearray.toByteArray +import com.infendro.bytearray.toULongArray +import com.infendro.hash.HashFunction +import kotlin.math.min + +@Suppress("UNUSED") +open class Keccak internal constructor( + override val bytes: Int, + private val rate: Int, + private val domain: Byte, +) : HashFunction() { + init { + if (rate % 8 != 0) { + throw IllegalArgumentException("rate must be divisible by 8") + } + } + + private val rc = ulongArrayOf( + 0x0000000000000001UL, 0x0000000000008082UL, 0x800000000000808aUL, 0x8000000080008000UL, + 0x000000000000808bUL, 0x0000000080000001UL, 0x8000000080008081UL, 0x8000000000008009UL, + 0x000000000000008aUL, 0x0000000000000088UL, 0x0000000080008009UL, 0x000000008000000aUL, + 0x000000008000808bUL, 0x800000000000008bUL, 0x8000000000008089UL, 0x8000000000008003UL, + 0x8000000000008002UL, 0x8000000000000080UL, 0x000000000000800aUL, 0x800000008000000aUL, + 0x8000000080008081UL, 0x8000000000008080UL, 0x0000000080000001UL, 0x8000000080008008UL + ) + + 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), + ) + + override fun hash( + value: ByteArray, + ): ByteArray { + val paddedValue = buildList { + addAll(value) + add(domain) + while ((size % rate) != (rate - 1)) { + add(0x00.toByte()) + } + add(0x80.toByte()) + } + + val state = ULongArray(25) + + // absorb + val chunks = paddedValue + .chunked(rate) + for (chunk in chunks) { + val numbers = chunk.toULongArray(LITTLE_ENDIAN) + for ((i, number) in numbers.withIndex()) { + state[i] = state[i] xor number + } + permute(state) + } + + // squeeze + return buildList(bytes) { + var i = 0 + while (size < bytes) { + if (i == rate / 8) { + permute(state) + i = 0 + } + addAll( + state[i++].toByteArray(LITTLE_ENDIAN) + .take(min(bytes - size, 8)) + ) + } + }.toByteArray() + } + + private fun permute( + a: ULongArray, + ) { + val b = ULongArray(25) + val c = ULongArray(5) + val d = ULongArray(5) + + repeat(24) { round -> + // θ + 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] + } + } +} diff --git a/src/commonMain/kotlin/com/infendro/hash/keccak/sha3/SHA3-224.kt b/src/commonMain/kotlin/com/infendro/hash/keccak/sha3/SHA3-224.kt new file mode 100644 index 0000000..ad515dd --- /dev/null +++ b/src/commonMain/kotlin/com/infendro/hash/keccak/sha3/SHA3-224.kt @@ -0,0 +1,6 @@ +package com.infendro.hash.keccak.sha3 + +import com.infendro.hash.keccak.Keccak + +@Suppress("UNUSED") +object `SHA3-224` : Keccak(28, 144, 0x06) diff --git a/src/commonMain/kotlin/com/infendro/hash/keccak/sha3/SHA3-256.kt b/src/commonMain/kotlin/com/infendro/hash/keccak/sha3/SHA3-256.kt new file mode 100644 index 0000000..14d7034 --- /dev/null +++ b/src/commonMain/kotlin/com/infendro/hash/keccak/sha3/SHA3-256.kt @@ -0,0 +1,6 @@ +package com.infendro.hash.keccak.sha3 + +import com.infendro.hash.keccak.Keccak + +@Suppress("UNUSED") +object `SHA3-256` : Keccak(32, 136, 0x06) diff --git a/src/commonMain/kotlin/com/infendro/hash/keccak/sha3/SHA3-384.kt b/src/commonMain/kotlin/com/infendro/hash/keccak/sha3/SHA3-384.kt new file mode 100644 index 0000000..176c9ae --- /dev/null +++ b/src/commonMain/kotlin/com/infendro/hash/keccak/sha3/SHA3-384.kt @@ -0,0 +1,6 @@ +package com.infendro.hash.keccak.sha3 + +import com.infendro.hash.keccak.Keccak + +@Suppress("UNUSED") +object `SHA3-384` : Keccak(48, 104, 0x06) diff --git a/src/commonMain/kotlin/com/infendro/hash/keccak/sha3/SHA3-512.kt b/src/commonMain/kotlin/com/infendro/hash/keccak/sha3/SHA3-512.kt new file mode 100644 index 0000000..0b605a8 --- /dev/null +++ b/src/commonMain/kotlin/com/infendro/hash/keccak/sha3/SHA3-512.kt @@ -0,0 +1,6 @@ +package com.infendro.hash.keccak.sha3 + +import com.infendro.hash.keccak.Keccak + +@Suppress("UNUSED") +object `SHA3-512` : Keccak(64, 72, 0x06) diff --git a/src/commonMain/kotlin/com/infendro/hash/keccak/shake/SHAKE128.kt b/src/commonMain/kotlin/com/infendro/hash/keccak/shake/SHAKE128.kt new file mode 100644 index 0000000..5ca9a8d --- /dev/null +++ b/src/commonMain/kotlin/com/infendro/hash/keccak/shake/SHAKE128.kt @@ -0,0 +1,6 @@ +package com.infendro.hash.keccak.shake + +import com.infendro.hash.keccak.Keccak + +@Suppress("UNUSED") +class SHAKE128(bytes: Int) : Keccak(bytes, 168, 0x1F) diff --git a/src/commonMain/kotlin/com/infendro/hash/keccak/shake/SHAKE256.kt b/src/commonMain/kotlin/com/infendro/hash/keccak/shake/SHAKE256.kt new file mode 100644 index 0000000..0bf9699 --- /dev/null +++ b/src/commonMain/kotlin/com/infendro/hash/keccak/shake/SHAKE256.kt @@ -0,0 +1,6 @@ +package com.infendro.hash.keccak.shake + +import com.infendro.hash.keccak.Keccak + +@Suppress("UNUSED") +class SHAKE256(bytes: Int) : Keccak(bytes, 136, 0x1F)