diff --git a/benchmarks/src/commonMain/kotlin/org/kotlincrypto/hash/benchmarks/SHA2Opts.kt b/benchmarks/src/commonMain/kotlin/org/kotlincrypto/hash/benchmarks/SHA2Opts.kt index 83edc0d..518095c 100644 --- a/benchmarks/src/commonMain/kotlin/org/kotlincrypto/hash/benchmarks/SHA2Opts.kt +++ b/benchmarks/src/commonMain/kotlin/org/kotlincrypto/hash/benchmarks/SHA2Opts.kt @@ -19,6 +19,7 @@ import kotlinx.benchmark.* import org.kotlincrypto.core.digest.Digest import org.kotlincrypto.hash.sha2.SHA256 import org.kotlincrypto.hash.sha2.SHA512 +import org.kotlincrypto.hash.sha2.SHA512_224 @State(Scope.Benchmark) @BenchmarkMode(Mode.AverageTime) @@ -37,3 +38,12 @@ open class SHA256Benchmark: DigestBenchmarkBase() { open class SHA512Benchmark: DigestBenchmarkBase() { override val d: Digest = SHA512() } + +@State(Scope.Benchmark) +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(BenchmarkTimeUnit.NANOSECONDS) +@Warmup(iterations = ITERATIONS, time = TIME_WARMUP) +@Measurement(iterations = ITERATIONS, time = TIME_MEASURE) +open class SHA512_224Benchmark: DigestBenchmarkBase() { + override val d: Digest = SHA512_224() +} diff --git a/library/md/src/commonMain/kotlin/org/kotlincrypto/hash/md/MD5.kt b/library/md/src/commonMain/kotlin/org/kotlincrypto/hash/md/MD5.kt index 2b3685b..e78cc8c 100644 --- a/library/md/src/commonMain/kotlin/org/kotlincrypto/hash/md/MD5.kt +++ b/library/md/src/commonMain/kotlin/org/kotlincrypto/hash/md/MD5.kt @@ -51,7 +51,7 @@ public class MD5: Digest { var c = state[2] var d = state[3] - for (i in 0 until blockSize()) { + for (i in 0.. { var j = (i * 4) + offset diff --git a/library/sha1/src/commonMain/kotlin/org/kotlincrypto/hash/sha1/SHA1.kt b/library/sha1/src/commonMain/kotlin/org/kotlincrypto/hash/sha1/SHA1.kt index 43285d7..d2d7185 100644 --- a/library/sha1/src/commonMain/kotlin/org/kotlincrypto/hash/sha1/SHA1.kt +++ b/library/sha1/src/commonMain/kotlin/org/kotlincrypto/hash/sha1/SHA1.kt @@ -44,7 +44,7 @@ public class SHA1: Digest { val x = x var j = offset - for (i in 0 until 16) { + for (i in 0..<16) { x[i] = ((input[j++].toInt() and 0xff) shl 24) or ((input[j++].toInt() and 0xff) shl 16) or @@ -52,7 +52,7 @@ public class SHA1: Digest { ((input[j++].toInt() and 0xff) ) } - for (i in 16 until 80) { + for (i in 16..<80) { x[i] = (x[i - 3] xor x[i - 8] xor x[i - 14] xor x[i - 16]) rotateLeft 1 } @@ -62,7 +62,7 @@ public class SHA1: Digest { var d = state[3] var e = state[4] - for (i in 0 until 80) { + for (i in 0..<80) { val a2 = when { i < 20 -> { val f = d xor (b and (c xor d)) diff --git a/library/sha2/build.gradle.kts b/library/sha2/build.gradle.kts index 6e412fd..b17a6b8 100644 --- a/library/sha2/build.gradle.kts +++ b/library/sha2/build.gradle.kts @@ -32,5 +32,18 @@ kmpConfiguration { } } } + + kotlin { + with(sourceSets) { + val nonJsSources = listOf("jvm", "native", "wasmJs", "wasmWasi").mapNotNull { + findByName(it + "Main") + } + if (nonJsSources.isEmpty()) return@kotlin + val nonJsMain = maybeCreate("nonJsMain").apply { + dependsOn(getByName("commonMain")) + } + nonJsSources.forEach { it.dependsOn(nonJsMain) } + } + } } } diff --git a/library/sha2/src/commonMain/kotlin/org/kotlincrypto/hash/sha2/Bit32Digest.kt b/library/sha2/src/commonMain/kotlin/org/kotlincrypto/hash/sha2/Bit32Digest.kt index e8e95b1..c11e7ab 100644 --- a/library/sha2/src/commonMain/kotlin/org/kotlincrypto/hash/sha2/Bit32Digest.kt +++ b/library/sha2/src/commonMain/kotlin/org/kotlincrypto/hash/sha2/Bit32Digest.kt @@ -84,7 +84,7 @@ public sealed class Bit32Digest: Digest { val x = x var j = offset - for (i in 0 until 16) { + for (i in 0..<16) { x[i] = ((input[j++].toInt() and 0xff) shl 24) or ((input[j++].toInt() and 0xff) shl 16) or @@ -92,7 +92,7 @@ public sealed class Bit32Digest: Digest { ((input[j++].toInt() and 0xff) ) } - for (i in 16 until 64) { + for (i in 16..<64) { val x15 = x[i - 15] val s0 = ((x15 ushr 7) or (x15 shl 25)) xor @@ -119,7 +119,7 @@ public sealed class Bit32Digest: Digest { var g = state[6] var h = state[7] - for (i in 0 until 64) { + for (i in 0..<64) { val s0 = ((a ushr 2) or (a shl 30)) xor ((a ushr 13) or (a shl 19)) xor diff --git a/library/sha2/src/commonMain/kotlin/org/kotlincrypto/hash/sha2/Bit64Digest.kt b/library/sha2/src/commonMain/kotlin/org/kotlincrypto/hash/sha2/Bit64Digest.kt index 74c0f28..775bdf7 100644 --- a/library/sha2/src/commonMain/kotlin/org/kotlincrypto/hash/sha2/Bit64Digest.kt +++ b/library/sha2/src/commonMain/kotlin/org/kotlincrypto/hash/sha2/Bit64Digest.kt @@ -20,6 +20,7 @@ package org.kotlincrypto.hash.sha2 import org.kotlincrypto.core.InternalKotlinCryptoApi import org.kotlincrypto.core.digest.Digest import org.kotlincrypto.core.digest.internal.DigestState +import org.kotlincrypto.hash.sha2.internal.rotateRight import kotlin.jvm.JvmField /** @@ -94,7 +95,7 @@ public sealed class Bit64Digest: Digest { val x = x var j = offset - for (i in 0 until 16) { + for (i in 0..<16) { x[i] = ((input[j++].toLong() and 0xff) shl 56) or ((input[j++].toLong() and 0xff) shl 48) or @@ -106,11 +107,11 @@ public sealed class Bit64Digest: Digest { ((input[j++].toLong() and 0xff) ) } - for (i in 16 until 80) { + for (i in 16..<80) { val x15 = x[i - 15] - val s0 = (x15 rotateRight 1) xor (x15 rotateRight 8) xor (x15 ushr 7) + val s0 = (x15.rotateRight(1)) xor (x15.rotateRight(8)) xor (x15 ushr 7) val x2 = x[i - 2] - val s1 = (x2 rotateRight 19) xor (x2 rotateRight 61) xor (x2 ushr 6) + val s1 = (x2.rotateRight(19)) xor (x2.rotateRight(61)) xor (x2 ushr 6) val x16 = x[i - 16] val x7 = x[i - 7] x[i] = x16 + s0 + x7 + s1 @@ -127,9 +128,9 @@ public sealed class Bit64Digest: Digest { var g = state[6] var h = state[7] - for (i in 0 until 80) { - val s0 = (a rotateRight 28) xor (a rotateRight 34) xor (a rotateRight 39) - val s1 = (e rotateRight 14) xor (e rotateRight 18) xor (e rotateRight 41) + for (i in 0..<80) { + val s0 = (a.rotateRight(28)) xor (a.rotateRight(34)) xor (a.rotateRight(39)) + val s1 = (e.rotateRight(14)) xor (e.rotateRight(18)) xor (e.rotateRight(41)) val ch = (e and f) xor (e.inv() and g) val maj = (a and b) xor (a and c) xor (b and c) @@ -215,9 +216,6 @@ public sealed class Bit64Digest: Digest { state[7] = h7 } - @Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress") - private inline infix fun Long.rotateRight(n: Int): Long = (this ushr n) or (this shl (64 - n)) - private companion object { private val K = longArrayOf( 4794697086780616226L, 8158064640168781261L, -5349999486874862801L, -1606136188198331460L, diff --git a/library/sha2/src/commonMain/kotlin/org/kotlincrypto/hash/sha2/SHA512t.kt b/library/sha2/src/commonMain/kotlin/org/kotlincrypto/hash/sha2/SHA512t.kt index e97fdc2..112a7dc 100644 --- a/library/sha2/src/commonMain/kotlin/org/kotlincrypto/hash/sha2/SHA512t.kt +++ b/library/sha2/src/commonMain/kotlin/org/kotlincrypto/hash/sha2/SHA512t.kt @@ -111,78 +111,79 @@ public class SHA512t: Bit64Digest { return ByteArray(0) } - val out = ByteArray(digestLength()) - var i = 0 + val len = digestLength() + val out = ByteArray(len) - fun Long.putOut() { - if (i == out.size) return + var i = 0 + fun Long.putOut(): Unit? { out[i++] = toByte() + return if (i == len) null else Unit } - (a shr 56).putOut() - (a shr 48).putOut() - (a shr 40).putOut() - (a shr 32).putOut() - (a shr 24).putOut() - (a shr 16).putOut() - (a shr 8).putOut() - (a ).putOut() - (b shr 56).putOut() - (b shr 48).putOut() - (b shr 40).putOut() - (b shr 32).putOut() - (b shr 24).putOut() - (b shr 16).putOut() - (b shr 8).putOut() - (b ).putOut() - (c shr 56).putOut() - (c shr 48).putOut() - (c shr 40).putOut() - (c shr 32).putOut() - (c shr 24).putOut() - (c shr 16).putOut() - (c shr 8).putOut() - (c ).putOut() - (d shr 56).putOut() - (d shr 48).putOut() - (d shr 40).putOut() - (d shr 32).putOut() - (d shr 24).putOut() - (d shr 16).putOut() - (d shr 8).putOut() - (d ).putOut() - (e shr 56).putOut() - (e shr 48).putOut() - (e shr 40).putOut() - (e shr 32).putOut() - (e shr 24).putOut() - (e shr 16).putOut() - (e shr 8).putOut() - (e ).putOut() - (f shr 56).putOut() - (f shr 48).putOut() - (f shr 40).putOut() - (f shr 32).putOut() - (f shr 24).putOut() - (f shr 16).putOut() - (f shr 8).putOut() - (f ).putOut() - (g shr 56).putOut() - (g shr 48).putOut() - (g shr 40).putOut() - (g shr 32).putOut() - (g shr 24).putOut() - (g shr 16).putOut() - (g shr 8).putOut() - (g ).putOut() - (h shr 56).putOut() - (h shr 48).putOut() - (h shr 40).putOut() - (h shr 32).putOut() - (h shr 24).putOut() - (h shr 16).putOut() - (h shr 8).putOut() - (h ).putOut() + (a shr 56).putOut() ?: return out + (a shr 48).putOut() ?: return out + (a shr 40).putOut() ?: return out + (a shr 32).putOut() ?: return out + (a shr 24).putOut() ?: return out + (a shr 16).putOut() ?: return out + (a shr 8).putOut() ?: return out + (a ).putOut() ?: return out + (b shr 56).putOut() ?: return out + (b shr 48).putOut() ?: return out + (b shr 40).putOut() ?: return out + (b shr 32).putOut() ?: return out + (b shr 24).putOut() ?: return out + (b shr 16).putOut() ?: return out + (b shr 8).putOut() ?: return out + (b ).putOut() ?: return out + (c shr 56).putOut() ?: return out + (c shr 48).putOut() ?: return out + (c shr 40).putOut() ?: return out + (c shr 32).putOut() ?: return out + (c shr 24).putOut() ?: return out + (c shr 16).putOut() ?: return out + (c shr 8).putOut() ?: return out + (c ).putOut() ?: return out + (d shr 56).putOut() ?: return out + (d shr 48).putOut() ?: return out + (d shr 40).putOut() ?: return out + (d shr 32).putOut() ?: return out + (d shr 24).putOut() ?: return out + (d shr 16).putOut() ?: return out + (d shr 8).putOut() ?: return out + (d ).putOut() ?: return out + (e shr 56).putOut() ?: return out + (e shr 48).putOut() ?: return out + (e shr 40).putOut() ?: return out + (e shr 32).putOut() ?: return out + (e shr 24).putOut() ?: return out + (e shr 16).putOut() ?: return out + (e shr 8).putOut() ?: return out + (e ).putOut() ?: return out + (f shr 56).putOut() ?: return out + (f shr 48).putOut() ?: return out + (f shr 40).putOut() ?: return out + (f shr 32).putOut() ?: return out + (f shr 24).putOut() ?: return out + (f shr 16).putOut() ?: return out + (f shr 8).putOut() ?: return out + (f ).putOut() ?: return out + (g shr 56).putOut() ?: return out + (g shr 48).putOut() ?: return out + (g shr 40).putOut() ?: return out + (g shr 32).putOut() ?: return out + (g shr 24).putOut() ?: return out + (g shr 16).putOut() ?: return out + (g shr 8).putOut() ?: return out + (g ).putOut() ?: return out + (h shr 56).putOut() ?: return out + (h shr 48).putOut() ?: return out + (h shr 40).putOut() ?: return out + (h shr 32).putOut() ?: return out + (h shr 24).putOut() ?: return out + (h shr 16).putOut() ?: return out + (h shr 8).putOut() ?: return out + (h ).putOut() ?: return out return out } diff --git a/benchmarks/src/commonMain/kotlin/org/kotlincrypto/hash/benchmarks/MDOpts.kt b/library/sha2/src/commonMain/kotlin/org/kotlincrypto/hash/sha2/internal/-CommonPlatform.kt similarity index 57% rename from benchmarks/src/commonMain/kotlin/org/kotlincrypto/hash/benchmarks/MDOpts.kt rename to library/sha2/src/commonMain/kotlin/org/kotlincrypto/hash/sha2/internal/-CommonPlatform.kt index f5494de..b20cfe4 100644 --- a/benchmarks/src/commonMain/kotlin/org/kotlincrypto/hash/benchmarks/MDOpts.kt +++ b/library/sha2/src/commonMain/kotlin/org/kotlincrypto/hash/sha2/internal/-CommonPlatform.kt @@ -13,17 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. **/ -package org.kotlincrypto.hash.benchmarks +@file:Suppress("KotlinRedundantDiagnosticSuppress") -import kotlinx.benchmark.* -import org.kotlincrypto.core.digest.Digest -import org.kotlincrypto.hash.md.MD5 +package org.kotlincrypto.hash.sha2.internal -@State(Scope.Benchmark) -@BenchmarkMode(Mode.AverageTime) -@OutputTimeUnit(BenchmarkTimeUnit.NANOSECONDS) -@Warmup(iterations = ITERATIONS, time = TIME_WARMUP) -@Measurement(iterations = ITERATIONS, time = TIME_MEASURE) -open class M55Benchmark: DigestBenchmarkBase() { - override val d: Digest = MD5() -} +@Suppress("NOTHING_TO_INLINE") +internal expect inline fun Long.rotateRight(n: Int): Long diff --git a/benchmarks/src/commonMain/kotlin/org/kotlincrypto/hash/benchmarks/SHA1Opts.kt b/library/sha2/src/jsMain/kotlin/org/kotlincrypto/hash/sha2/internal/-JsPlatform.kt similarity index 57% rename from benchmarks/src/commonMain/kotlin/org/kotlincrypto/hash/benchmarks/SHA1Opts.kt rename to library/sha2/src/jsMain/kotlin/org/kotlincrypto/hash/sha2/internal/-JsPlatform.kt index 34cf98f..8cab88d 100644 --- a/benchmarks/src/commonMain/kotlin/org/kotlincrypto/hash/benchmarks/SHA1Opts.kt +++ b/library/sha2/src/jsMain/kotlin/org/kotlincrypto/hash/sha2/internal/-JsPlatform.kt @@ -13,17 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. **/ -package org.kotlincrypto.hash.benchmarks +@file:Suppress("KotlinRedundantDiagnosticSuppress") -import kotlinx.benchmark.* -import org.kotlincrypto.core.digest.Digest -import org.kotlincrypto.hash.sha1.SHA1 +package org.kotlincrypto.hash.sha2.internal -@State(Scope.Benchmark) -@BenchmarkMode(Mode.AverageTime) -@OutputTimeUnit(BenchmarkTimeUnit.NANOSECONDS) -@Warmup(iterations = ITERATIONS, time = TIME_WARMUP) -@Measurement(iterations = ITERATIONS, time = TIME_MEASURE) -open class SHA1Benchmark: DigestBenchmarkBase() { - override val d: Digest = SHA1() -} +import kotlin.rotateRight as kRotateRight + +@Suppress("NOTHING_TO_INLINE") +internal actual inline fun Long.rotateRight(n: Int): Long = kRotateRight(n) diff --git a/library/sha2/src/nonJsMain/kotlin/org/kotlincrypto/hash/sha2/internal/-NonJsPlatform.kt b/library/sha2/src/nonJsMain/kotlin/org/kotlincrypto/hash/sha2/internal/-NonJsPlatform.kt new file mode 100644 index 0000000..6d7e5f0 --- /dev/null +++ b/library/sha2/src/nonJsMain/kotlin/org/kotlincrypto/hash/sha2/internal/-NonJsPlatform.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024 Matthew Nelson + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +@file:Suppress("KotlinRedundantDiagnosticSuppress") + +package org.kotlincrypto.hash.sha2.internal + +@Suppress("NOTHING_TO_INLINE") +internal actual inline fun Long.rotateRight(n: Int): Long = (this ushr n) or (this shl (64 - n)) diff --git a/library/sha3/src/commonMain/kotlin/org/kotlincrypto/hash/sha3/KeccakDigest.kt b/library/sha3/src/commonMain/kotlin/org/kotlincrypto/hash/sha3/KeccakDigest.kt index 779e1da..7fe2307 100644 --- a/library/sha3/src/commonMain/kotlin/org/kotlincrypto/hash/sha3/KeccakDigest.kt +++ b/library/sha3/src/commonMain/kotlin/org/kotlincrypto/hash/sha3/KeccakDigest.kt @@ -21,7 +21,6 @@ import org.kotlincrypto.core.InternalKotlinCryptoApi import org.kotlincrypto.core.digest.Digest import org.kotlincrypto.core.digest.internal.DigestState import org.kotlincrypto.endians.LittleEndian -import org.kotlincrypto.endians.LittleEndian.Companion.toLittleEndian import org.kotlincrypto.sponges.keccak.F1600 import org.kotlincrypto.sponges.keccak.keccakP import kotlin.experimental.xor @@ -70,17 +69,28 @@ public sealed class KeccakDigest: Digest { protected final override fun compress(input: ByteArray, offset: Int) { val A = state - val b = input - var i = 0 - var o = offset - val limit = o + blockSize() + var APos = 0 + var inputPos = offset + val inputLimit = inputPos + blockSize() // Max blockSize is 168 (SHAKE128), so at a maximum only // 21 out of 25 state values will ever be modified with - // input for each compression/permutation. - while (o < limit) { - A.addData(i++, LittleEndian.bytesToLong(b[o++], b[o++], b[o++], b[o++], b[o++], b[o++], b[o++], b[o++])) + // input for each permutation. + while (inputPos < inputLimit) { + A.addData( + index = APos++, + data = LittleEndian.bytesToLong( + input[inputPos++], + input[inputPos++], + input[inputPos++], + input[inputPos++], + input[inputPos++], + input[inputPos++], + input[inputPos++], + input[inputPos++], + ) + ) } A.keccakP() @@ -89,80 +99,82 @@ public sealed class KeccakDigest: Digest { protected override fun digest(bitLength: Long, bufferOffset: Int, buffer: ByteArray): ByteArray { buffer[bufferOffset] = dsByte buffer.fill(0, bufferOffset + 1) - buffer[buffer.lastIndex] = buffer.last() xor 0x80.toByte() + val iLast = buffer.lastIndex + buffer[iLast] = buffer[iLast] xor 0x80.toByte() compress(buffer, 0) - return extract(state, ByteArray(digestLength()), 0, digestLength(), 0L) + val len = digestLength() + return extract(state, ByteArray(len), 0, len, 0L) } protected open fun extract(A: F1600, out: ByteArray, offset: Int, len: Int, bytesRead: Long): ByteArray { - val spongeSize: Int = blockSize() - - // Bytes remaining in the sponge to be extracted - // before a permutation is needed - var spongeRemaining: Int = spongeSize - (bytesRead % spongeSize).toInt() - - var b = offset - val limit = b + len - - fun writeData(data: LittleEndian): Int { - var j = when { - spongeRemaining < data.size -> { - // if there is 6 bytes remaining in the sponge - // 8 - 6 = 2 - // j: 2, 3, 4, 5, 6, 7 = 6 bytes written - // - // if there is 0 bytes remaining in the sponge - // 8 - 0 = 8 - // j > 8 so no bytes will be written - data.size - spongeRemaining + var outPos = offset + val outLimit = outPos + len + + val spongeSize = blockSize() + val spongeLimit = (spongeSize / Long.SIZE_BYTES) + 1 + + // Bytes available in the sponge for extraction before another permutation is needed + var spongeRem = spongeSize - (bytesRead % spongeSize).toInt() + var spongePos = (spongeSize - spongeRem) / Long.SIZE_BYTES + + while (outPos < outLimit) { + while (spongePos < spongeLimit) { + val lane = A[spongePos++] + + val laneOffset = when { + spongeRem < Long.SIZE_BYTES -> { + // If there is 6 bytes remaining in the sponge + // 8 - 6 = 2 + // indices: 2, 3, 4, 5, 6, 7 = 6 bytes to read out + // + // If there is 0 bytes remaining in the sponge + // 8 - 0 = 8 + // indices: 8 so no bytes will be written + Long.SIZE_BYTES - spongeRem + } + outPos == offset -> { + // Must check first block of the extraction + // if it is a partial read (e.g. prior extract + // call was for 10 bytes, leaving 6 remaining + // in this block of the sponge). + val priorRem = spongeRem % Long.SIZE_BYTES + if (priorRem == 0) { + // Full block. Copy all of it. + 0 + } else { + // Partial block. Copy remainder. + Long.SIZE_BYTES - priorRem + } + } + else -> 0 } - b == offset -> { - // Must check first block of the extraction - // if it is a partial read (e.g. last read was - // 10 bytes, leaving 6 bytes of the next block - // in the sponge remaining to be used for this - // read). - val remainder = spongeRemaining % data.size - - if (remainder == 0) 0 else data.size - remainder - } - else -> 0 - } - var written = 0 - while (b < limit && j < data.size) { - out[b++] = data[j++] - written++ - } - - spongeRemaining -= written - return written - } - - val iLimit = (blockSize() / Long.SIZE_BYTES) + 1 - var i = (spongeSize - spongeRemaining) / Long.SIZE_BYTES - while (b < limit) { - while (i < iLimit) { - val data = A[i++].toLittleEndian() + var i = 0 + while (outPos < outLimit && (laneOffset + i) < Long.SIZE_BYTES) { + val bits = (laneOffset + i++) * Long.SIZE_BYTES + out[outPos++] = (lane ushr bits).toByte() + } - // Either no more room in out, or sponge was exhausted - if (writeData(data) == 0) break + if (i == 0) { + // Either sponge is exhausted, or out is full. + break + } + spongeRem -= i } - if (spongeRemaining == 0) { + if (spongeRem == 0) { + // Do another permutation to refill the sponge A.keccakP() - i = 0 - spongeRemaining = spongeSize + spongePos = 0 + spongeRem = spongeSize } } return out } - protected override fun resetDigest() { - state.reset() - } + protected override fun resetDigest() { state.reset() } protected companion object { internal const val KECCAK: String = "Keccak" diff --git a/library/sha3/src/commonMain/kotlin/org/kotlincrypto/hash/sha3/ParallelDigest.kt b/library/sha3/src/commonMain/kotlin/org/kotlincrypto/hash/sha3/ParallelDigest.kt index 71c1687..acb7d2e 100644 --- a/library/sha3/src/commonMain/kotlin/org/kotlincrypto/hash/sha3/ParallelDigest.kt +++ b/library/sha3/src/commonMain/kotlin/org/kotlincrypto/hash/sha3/ParallelDigest.kt @@ -33,7 +33,7 @@ import org.kotlincrypto.core.xof.Xof public sealed class ParallelDigest: SHAKEDigest { private val inner: SHAKEDigest - private val innerBuffer: ByteArray + private val innerBuf: ByteArray private var innerBufOffs: Int private var processCount: Long @@ -58,18 +58,18 @@ public sealed class ParallelDigest: SHAKEDigest { BIT_STRENGTH_256 -> CSHAKE256(null, null) else -> throw IllegalArgumentException("bitStrength must be $BIT_STRENGTH_128 or $BIT_STRENGTH_256") } - this.innerBuffer = ByteArray(B) + this.innerBuf = ByteArray(B) this.innerBufOffs = 0 this.processCount = 0L @OptIn(InternalKotlinCryptoApi::class) - val encB = Xof.Utils.leftEncode(innerBuffer.size.toLong()) + val encB = Xof.Utils.leftEncode(B.toLong()) super.updateDigest(encB, 0, encB.size) } protected constructor(state: DigestState, digest: ParallelDigest): super(state, digest) { this.inner = digest.inner.copy() as SHAKEDigest - this.innerBuffer = digest.innerBuffer.copyOf() + this.innerBuf = digest.innerBuf.copyOf() this.innerBufOffs = digest.innerBufOffs this.processCount = digest.processCount } @@ -79,7 +79,7 @@ public sealed class ParallelDigest: SHAKEDigest { // If there's any buffered bytes left, // process them to append them to the // buffer here as additional input. - inner.update(innerBuffer, 0, innerBufOffs) + inner.update(innerBuf, 0, innerBufOffs) processCount++ innerBufOffs = 0 inner.digest() @@ -111,46 +111,59 @@ public sealed class ParallelDigest: SHAKEDigest { } protected final override fun updateDigest(input: Byte) { - innerBuffer[innerBufOffs] = input - - if (++innerBufOffs != innerBuffer.size) return - processBlock(innerBuffer, 0) + val offsBuf = innerBufOffs++ + innerBuf[offsBuf] = input + if (offsBuf + 1 != innerBuf.size) return + processBlock(innerBuf, 0) innerBufOffs = 0 } protected final override fun updateDigest(input: ByteArray, offset: Int, len: Int) { - var i = offset - var remaining = len - - // fill buffer if not already empty - while (innerBufOffs != 0 && remaining > 0) { - updateDigest(input[i++]) - remaining-- + val buf = innerBuf + val blockSize = buf.size + var offsInput = offset + val limit = offsInput + len + var offsBuf = innerBufOffs + + if (offsBuf > 0) { + if (offsBuf + len < blockSize) { + input.copyInto(buf, offsBuf, offsInput, limit) + innerBufOffs = offsBuf + len + return + } + + val needed = blockSize - offsBuf + input.copyInto(buf, offsBuf, offsInput, offsInput + needed) + processBlock(buf, 0) + offsBuf = 0 + offsInput += needed } - // chunk - while (remaining >= innerBuffer.size) { - processBlock(input, i) - i += innerBuffer.size - remaining -= innerBuffer.size - } + while (offsInput < limit) { + val offsNext = offsInput + blockSize - // add remaining to buffer - while (remaining-- > 0) { - updateDigest(input[i++]) + if (offsNext > limit) { + input.copyInto(buf, 0, offsInput, limit) + offsBuf = limit - offsInput + break + } + + processBlock(input, offsInput) + offsInput = offsNext } + + innerBufOffs = offsBuf } private fun processBlock(input: ByteArray, offset: Int) { - inner.update(input, offset, innerBuffer.size) + inner.update(input, offset, innerBuf.size) super.updateDigest(inner.digest(), 0, inner.digestLength()) - processCount++ } protected final override fun resetDigest() { super.resetDigest() - this.innerBuffer.fill(0) + this.innerBuf.fill(0) this.innerBufOffs = 0 this.processCount = 0L @@ -160,7 +173,7 @@ public sealed class ParallelDigest: SHAKEDigest { // this.inner.reset() @OptIn(InternalKotlinCryptoApi::class) - val encB = Xof.Utils.leftEncode(innerBuffer.size.toLong()) + val encB = Xof.Utils.leftEncode(innerBuf.size.toLong()) super.updateDigest(encB, 0, encB.size) } diff --git a/library/sha3/src/commonMain/kotlin/org/kotlincrypto/hash/sha3/SHAKEDigest.kt b/library/sha3/src/commonMain/kotlin/org/kotlincrypto/hash/sha3/SHAKEDigest.kt index e95cf20..6d982ef 100644 --- a/library/sha3/src/commonMain/kotlin/org/kotlincrypto/hash/sha3/SHAKEDigest.kt +++ b/library/sha3/src/commonMain/kotlin/org/kotlincrypto/hash/sha3/SHAKEDigest.kt @@ -19,7 +19,6 @@ import org.kotlincrypto.core.* import org.kotlincrypto.core.digest.internal.DigestState import org.kotlincrypto.core.xof.* import org.kotlincrypto.endians.LittleEndian -import org.kotlincrypto.endians.LittleEndian.Companion.toLittleEndian import org.kotlincrypto.sponges.keccak.F1600 import kotlin.jvm.JvmStatic import kotlin.jvm.JvmSynthetic @@ -95,8 +94,16 @@ public sealed class SHAKEDigest: KeccakDigest, XofAlgorithm { // newReader called digest(). Snipe the extraction // and pass it the current state in bytes. val newOut = ByteArray(A.size * Long.SIZE_BYTES) - for (i in A.indices) { - A[i].toLittleEndian().copyInto(newOut, i * Long.SIZE_BYTES) + var j = 0 + A.forEach { lane -> + newOut[j++] = (lane ).toByte() + newOut[j++] = (lane ushr 8).toByte() + newOut[j++] = (lane ushr 16).toByte() + newOut[j++] = (lane ushr 24).toByte() + newOut[j++] = (lane ushr 32).toByte() + newOut[j++] = (lane ushr 40).toByte() + newOut[j++] = (lane ushr 48).toByte() + newOut[j++] = (lane ushr 56).toByte() } isReadingXof = true return newOut @@ -152,32 +159,41 @@ public sealed class SHAKEDigest: KeccakDigest, XofAlgorithm { // xOfMode, the returned bytes will be the entire contents // of its final state such that it can be rebuilt here in // order to be used for variable output length reads. - val state: F1600 = delegateCopy.digest().let { A -> - val f1600 = F1600() - - var b = 0 - for (i in f1600.indices) { - f1600.addData( - i, - LittleEndian.bytesToLong(A[b++], A[b++], A[b++], A[b++], A[b++], A[b++], A[b++], A[b++]) + val A: F1600 = delegateCopy.digest().let { b -> + val new = F1600() + + var j = 0 + for (i in new.indices) { + new.addData( + index = i, + data = LittleEndian.bytesToLong( + b[j++], + b[j++], + b[j++], + b[j++], + b[j++], + b[j++], + b[j++], + b[j++], + ) ) } - A.fill(0) + b.fill(0) - f1600 + new } return object : Reader() { override fun readProtected(out: ByteArray, offset: Int, len: Int, bytesRead: Long) { - delegateCopy.extract(state, out, offset, len, bytesRead) + delegateCopy.extract(A, out, offset, len, bytesRead) } override fun closeProtected() { // delegateCopy was already reset when digest() // was called in order to pass state. - state.reset() + A.reset() } } }