Implement streaming API

This commit is contained in:
2026-03-11 18:53:00 +01:00
parent cb3ff7ec6c
commit 328d4b957e
34 changed files with 936 additions and 593 deletions

View File

@@ -4,68 +4,60 @@ import com.infendro.bytes.ByteOrder.LITTLE_ENDIAN
import com.infendro.bytes.bytearray.copyInto
import com.infendro.bytes.long.copyInto
import com.infendro.hash.HashFunction
import com.infendro.hash.util.align
open class Keccak(
override val bytes: Int,
bytes: Int,
private val rate: Int,
private val domain: Byte,
) : HashFunction {
) : HashFunction(bytes, rate) {
init {
require(bytes > 0)
require(rate in 8..192 step 8)
}
override val block: Int
get() = rate
private var state = LongArray(25)
private val rc = longArrayOf(
+0x0000000000000001, +0x0000000000008082, -0x7fffffffffff7f76, -0x7fffffff7fff8000,
+0x000000000000808b, +0x0000000080000001, -0x7fffffff7fff7f7f, -0x7fffffffffff7ff7,
+0x000000000000008a, +0x0000000000000088, +0x0000000080008009, +0x000000008000000a,
+0x000000008000808b, -0x7fffffffffffff75, -0x7fffffffffff7f77, -0x7fffffffffff7ffd,
-0x7fffffffffff7ffe, -0x7fffffffffffff80, +0x000000000000800a, -0x7fffffff7ffffff6,
-0x7fffffff7fff7f7f, -0x7fffffffffff7f80, +0x0000000080000001, -0x7fffffff7fff7ff8,
)
private val w = LongArray(block / 8)
private val b = LongArray(25)
private val c = LongArray(5)
private val d = LongArray(5)
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 hashInto(value: ByteArray, destination: ByteArray, destinationOffset: Int) {
val input = pad(value)
val state = LongArray(25)
for (i in input.indices step block) {
val block = input.sliceArray(i..<(i + block))
absorb(state, block)
permute(state)
}
squeeze(state, destination, destinationOffset)
override fun reset() {
super.reset()
state.fill(0L)
}
private fun pad(value: ByteArray): ByteArray {
val size = (value.size + 2).align(block)
return ByteArray(size).also {
value.copyInto(it)
it[value.size] = domain
it[size - 1] = -0x80
}
override fun process() {
absorb(buffer.value)
permute()
}
private fun absorb(state: LongArray, block: ByteArray) {
val w = LongArray(this.block / 8)
private fun finalize() {
buffer.append(domain)
if (buffer.free < 1) {
process()
buffer.reset()
}
buffer.jumpTo(block - 1)
buffer.append(0x80.toByte())
process()
}
override fun digestInto(destination: ByteArray, destinationOffset: Int) {
finalize()
squeeze(destination, destinationOffset)
reset()
}
private fun absorb(block: ByteArray) {
block.copyInto(w, order = LITTLE_ENDIAN)
for (i in w.indices) {
state[i] = state[i] xor w[i]
}
}
private fun squeeze(state: LongArray, destination: ByteArray, destinationOffset: Int) {
private fun squeeze(destination: ByteArray, destinationOffset: Int) {
val buffer = ByteArray(8)
val threshold = rate / 8
@@ -73,7 +65,7 @@ open class Keccak(
var i = 0
while (consumed < bytes) {
if (i == threshold) {
permute(state)
permute()
i = 0
}
@@ -86,10 +78,8 @@ open class Keccak(
}
}
private fun permute(a: LongArray) = repeat(24) { round ->
val b = LongArray(25)
val c = LongArray(5)
val d = LongArray(5)
private fun permute() = repeat(24) { round ->
val a = state
// θ
for (x in 0..4) {
@@ -115,4 +105,23 @@ open class Keccak(
// ι
a[0] = a[0] xor rc[round]
}
companion object {
private val rc = longArrayOf(
+0x0000000000000001, +0x0000000000008082, -0x7fffffffffff7f76, -0x7fffffff7fff8000,
+0x000000000000808b, +0x0000000080000001, -0x7fffffff7fff7f7f, -0x7fffffffffff7ff7,
+0x000000000000008a, +0x0000000000000088, +0x0000000080008009, +0x000000008000000a,
+0x000000008000808b, -0x7fffffffffffff75, -0x7fffffffffff7f77, -0x7fffffffffff7ffd,
-0x7fffffffffff7ffe, -0x7fffffffffffff80, +0x000000000000800a, -0x7fffffff7ffffff6,
-0x7fffffff7fff7f7f, -0x7fffffffffff7f80, +0x0000000080000001, -0x7fffffff7fff7ff8,
)
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),
)
}
}