From 768e3e950552bbf685321349f086cb1a54aba2bb Mon Sep 17 00:00:00 2001 From: Matthew Nelson Date: Thu, 2 Jan 2025 16:03:39 -0500 Subject: [PATCH 1/5] Add left/right encod unit test --- .../kotlincrypto/core/xof/XofUtilsUnitTest.kt | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/library/xof/src/commonTest/kotlin/org/kotlincrypto/core/xof/XofUtilsUnitTest.kt b/library/xof/src/commonTest/kotlin/org/kotlincrypto/core/xof/XofUtilsUnitTest.kt index 5745fae..3bba72d 100644 --- a/library/xof/src/commonTest/kotlin/org/kotlincrypto/core/xof/XofUtilsUnitTest.kt +++ b/library/xof/src/commonTest/kotlin/org/kotlincrypto/core/xof/XofUtilsUnitTest.kt @@ -22,17 +22,40 @@ import kotlin.test.assertContentEquals @OptIn(InternalKotlinCryptoApi::class) class XofUtilsUnitTest { + @Test + fun givenValue_whenEncoded_thenIsAsExpected() { + listOf( + 777711L to byteArrayOf(3, 11, -35, -17), + -777711L to byteArrayOf(8, -1, -1, -1, -1, -1, -12, 34, 17), + 555L to byteArrayOf(2, 2, 43), + Long.MIN_VALUE to byteArrayOf(8, -128, 0, 0, 0, 0, 0, 0, 0), + Long.MAX_VALUE to byteArrayOf(8, 127, -1, -1, -1, -1, -1, -1, -1), + ).forEach { (value, expected) -> + assertContentEquals(expected, Xof.Utils.leftEncode(value)) + + // Shift expected for right encoding + var i = 0 + while (i < expected.lastIndex) { + val old = expected[i] + val new = expected[i + 1] + expected[i] = new + expected[i + 1] = old + i++ + } + + assertContentEquals(expected, Xof.Utils.rightEncode(value)) + } + } + @Test fun givenLeftEncoding_whenValueZero_thenResultIsAsExpected() { val expected = ByteArray(2).apply { this[0] = 1 } - val actual = Xof.Utils.leftEncode(0L) - assertContentEquals(expected, actual) + assertContentEquals(expected, Xof.Utils.leftEncode(0L)) } @Test fun givenRightEncoding_whenValueZero_thenResultIsAsExpected() { val expected = ByteArray(2).apply { this[1] = 1 } - val actual = Xof.Utils.rightEncode(0L) - assertContentEquals(expected, actual) + assertContentEquals(expected, Xof.Utils.rightEncode(0L)) } } From dde6c517a678ae0b71506ff56244204ff52b9e71 Mon Sep 17 00:00:00 2001 From: Matthew Nelson Date: Thu, 2 Jan 2025 16:12:42 -0500 Subject: [PATCH 2/5] Add benchmarks for Xof.Utils --- benchmarks/README.md | 3 +- benchmarks/build.gradle.kts | 2 + .../{DigestBenchmark.kt => DigestOps.kt} | 0 .../kotlincrypto/core/benchmarks/XofOps.kt | 37 +++++++++++++++++++ 4 files changed, 40 insertions(+), 2 deletions(-) rename benchmarks/src/commonMain/kotlin/org/kotlincrypto/core/benchmarks/{DigestBenchmark.kt => DigestOps.kt} (100%) create mode 100644 benchmarks/src/commonMain/kotlin/org/kotlincrypto/core/benchmarks/XofOps.kt diff --git a/benchmarks/README.md b/benchmarks/README.md index 4d09c09..4813323 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -1,7 +1,6 @@ # benchmarks -Benchmarks for tracking performance of `core` implementation. Currently, the only -thing worth benchmarking is the `Digest` implementation. +Benchmarks for tracking performance of `core` implementation. **NOTE:** Benchmarking is run on every Pull Request. Results can be viewed for each workflow run on the [GitHub Actions][url-actions] tab of the repository. diff --git a/benchmarks/build.gradle.kts b/benchmarks/build.gradle.kts index 2cdf902..9fee071 100644 --- a/benchmarks/build.gradle.kts +++ b/benchmarks/build.gradle.kts @@ -54,7 +54,9 @@ kmpConfiguration { sourceSetMain { dependencies { implementation(libs.benchmark.runtime) + implementation(libs.kotlincrypto.endians.endians) implementation(project(":library:digest")) + implementation(project(":library:xof")) } } } diff --git a/benchmarks/src/commonMain/kotlin/org/kotlincrypto/core/benchmarks/DigestBenchmark.kt b/benchmarks/src/commonMain/kotlin/org/kotlincrypto/core/benchmarks/DigestOps.kt similarity index 100% rename from benchmarks/src/commonMain/kotlin/org/kotlincrypto/core/benchmarks/DigestBenchmark.kt rename to benchmarks/src/commonMain/kotlin/org/kotlincrypto/core/benchmarks/DigestOps.kt diff --git a/benchmarks/src/commonMain/kotlin/org/kotlincrypto/core/benchmarks/XofOps.kt b/benchmarks/src/commonMain/kotlin/org/kotlincrypto/core/benchmarks/XofOps.kt new file mode 100644 index 0000000..3aa4200 --- /dev/null +++ b/benchmarks/src/commonMain/kotlin/org/kotlincrypto/core/benchmarks/XofOps.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2025 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. + **/ +package org.kotlincrypto.core.benchmarks + +import kotlinx.benchmark.* +import org.kotlincrypto.core.InternalKotlinCryptoApi +import org.kotlincrypto.core.xof.Xof +import kotlin.random.Random + +@State(Scope.Benchmark) +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(BenchmarkTimeUnit.NANOSECONDS) +@Warmup(iterations = 5, time = 2) +@Measurement(iterations = 5, time = 3) +@OptIn(InternalKotlinCryptoApi::class) +open class XofUtilsBenchmark { + + private val longs = LongArray(10) { Random.Default.nextLong() } + + @Benchmark + fun leftEncode() { + longs.forEach { long -> Xof.Utils.leftEncode(long) } + } +} From 7d6a008632e9987fb46588f14086f774e5fdf0ac Mon Sep 17 00:00:00 2001 From: Matthew Nelson Date: Thu, 2 Jan 2025 17:08:06 -0500 Subject: [PATCH 3/5] Add ability to pass Int value or hi/lo Long representations to left/right encode --- library/xof/api/xof.api | 4 + library/xof/api/xof.klib.api | 4 + .../kotlin/org/kotlincrypto/core/xof/Xof.kt | 83 +++++++++++++------ .../kotlincrypto/core/xof/XofUtilsUnitTest.kt | 36 ++++++-- 4 files changed, 93 insertions(+), 34 deletions(-) diff --git a/library/xof/api/xof.api b/library/xof/api/xof.api index 3d50cca..56fd791 100644 --- a/library/xof/api/xof.api +++ b/library/xof/api/xof.api @@ -24,7 +24,11 @@ public abstract class org/kotlincrypto/core/xof/Xof$Reader { public final class org/kotlincrypto/core/xof/Xof$Utils { public static final field INSTANCE Lorg/kotlincrypto/core/xof/Xof$Utils; + public static final fun leftEncode (I)[B + public static final fun leftEncode (II)[B public static final fun leftEncode (J)[B + public static final fun rightEncode (I)[B + public static final fun rightEncode (II)[B public static final fun rightEncode (J)[B } diff --git a/library/xof/api/xof.klib.api b/library/xof/api/xof.klib.api index 1295b16..76cca6b 100644 --- a/library/xof/api/xof.klib.api +++ b/library/xof/api/xof.klib.api @@ -56,7 +56,11 @@ sealed class <#A: org.kotlincrypto.core.xof/XofAlgorithm> org.kotlincrypto.core. } final object Utils { // org.kotlincrypto.core.xof/Xof.Utils|null[0] + final fun leftEncode(kotlin/Int): kotlin/ByteArray // org.kotlincrypto.core.xof/Xof.Utils.leftEncode|leftEncode(kotlin.Int){}[0] + final fun leftEncode(kotlin/Int, kotlin/Int): kotlin/ByteArray // org.kotlincrypto.core.xof/Xof.Utils.leftEncode|leftEncode(kotlin.Int;kotlin.Int){}[0] final fun leftEncode(kotlin/Long): kotlin/ByteArray // org.kotlincrypto.core.xof/Xof.Utils.leftEncode|leftEncode(kotlin.Long){}[0] + final fun rightEncode(kotlin/Int): kotlin/ByteArray // org.kotlincrypto.core.xof/Xof.Utils.rightEncode|rightEncode(kotlin.Int){}[0] + final fun rightEncode(kotlin/Int, kotlin/Int): kotlin/ByteArray // org.kotlincrypto.core.xof/Xof.Utils.rightEncode|rightEncode(kotlin.Int;kotlin.Int){}[0] final fun rightEncode(kotlin/Long): kotlin/ByteArray // org.kotlincrypto.core.xof/Xof.Utils.rightEncode|rightEncode(kotlin.Long){}[0] } } diff --git a/library/xof/src/commonMain/kotlin/org/kotlincrypto/core/xof/Xof.kt b/library/xof/src/commonMain/kotlin/org/kotlincrypto/core/xof/Xof.kt index 7379c1f..70226e5 100644 --- a/library/xof/src/commonMain/kotlin/org/kotlincrypto/core/xof/Xof.kt +++ b/library/xof/src/commonMain/kotlin/org/kotlincrypto/core/xof/Xof.kt @@ -16,7 +16,6 @@ package org.kotlincrypto.core.xof import org.kotlincrypto.core.* -import org.kotlincrypto.endians.BigEndian.Companion.toBigEndian import kotlin.jvm.JvmName import kotlin.jvm.JvmOverloads import kotlin.jvm.JvmStatic @@ -204,50 +203,82 @@ public sealed class Xof: Algorithm, Copyable>, Resettabl public object Utils { @JvmStatic - public fun leftEncode(value: Long): ByteArray { - // If it's zero, return early with [1, 0] - if (value == 0L) return ByteArray(2).apply { this[0] = 1 } - - val be = value.toBigEndian() + public fun leftEncode(value: Int): ByteArray { + return encode(lo = value, hi = 0, left = true) + } - // Find index of first non-zero byte - var i = 0 - while (i < be.size && be[i] == 0.toByte()) { - i++ - } + @JvmStatic + public fun leftEncode(value: Long): ByteArray { + val lo = value.toInt() + val hi = value.rotateLeft(32).toInt() + return encode(lo = lo, hi = hi, left = true) + } - val b = ByteArray(be.size - i + 1) + @JvmStatic + public fun leftEncode(lo: Int, hi: Int): ByteArray { + return encode(lo = lo, hi = hi, left = true) + } - // Prepend with number of non-zero bytes - b[0] = (be.size - i).toByte() + @JvmStatic + public fun rightEncode(value: Int): ByteArray { + return encode(lo = value, hi = 0, left = false) + } - be.copyInto(b, 1, i) + @JvmStatic + public fun rightEncode(value: Long): ByteArray { + val lo = value.toInt() + val hi = value.rotateLeft(32).toInt() + return encode(lo = lo, hi = hi, left = false) + } - return b + @JvmStatic + public fun rightEncode(lo: Int, hi: Int): ByteArray { + return encode(lo = lo, hi = hi, left = false) } @JvmStatic - public fun rightEncode(value: Long): ByteArray { - // If it's zero, return early with [0, 1] - if (value == 0L) return ByteArray(2).apply { this[1] = 1 } + private fun encode(lo: Int, hi: Int, left: Boolean): ByteArray { + if (lo == 0 && hi == 0) { + // If it's zero, return early + return if (left) byteArrayOf(1, 0) else byteArrayOf(0, 1) + } - val be = value.toBigEndian() + val a = byteArrayOf( + (hi ushr 24).toByte(), + (hi ushr 16).toByte(), + (hi ushr 8).toByte(), + (hi ).toByte(), + (lo ushr 24).toByte(), + (lo ushr 16).toByte(), + (lo ushr 8).toByte(), + (lo ).toByte(), + ) // Find index of first non-zero byte var i = 0 - while (i < be.size && be[i] == 0.toByte()) { + while (i < a.size && a[i] == ZERO) { i++ } - val b = ByteArray(be.size - i + 1) - - // Append with number of non-zero bytes - b[b.lastIndex] = (be.size - i).toByte() + val b = ByteArray(a.size - i + 1) + val num = (a.size - i).toByte() + + val offset = if (left) { + // Prepend with number of non-zero bytes + b[0] = num + 1 + } else { + // Append with number of non-zero bytes + b[b.lastIndex] = num + 0 + } - be.copyInto(b, 0, i) + a.copyInto(b, offset, i) return b } + + private const val ZERO: Byte = 0 } protected abstract fun newReader(): Reader diff --git a/library/xof/src/commonTest/kotlin/org/kotlincrypto/core/xof/XofUtilsUnitTest.kt b/library/xof/src/commonTest/kotlin/org/kotlincrypto/core/xof/XofUtilsUnitTest.kt index 3bba72d..063907f 100644 --- a/library/xof/src/commonTest/kotlin/org/kotlincrypto/core/xof/XofUtilsUnitTest.kt +++ b/library/xof/src/commonTest/kotlin/org/kotlincrypto/core/xof/XofUtilsUnitTest.kt @@ -22,16 +22,36 @@ import kotlin.test.assertContentEquals @OptIn(InternalKotlinCryptoApi::class) class XofUtilsUnitTest { + private sealed class Data private constructor() { + class L(val value: Long): Data() { + override fun leftEncode(): ByteArray = Xof.Utils.leftEncode(value) + override fun rightEncode(): ByteArray = Xof.Utils.rightEncode(value) + } + class I(val value: Int): Data() { + override fun leftEncode(): ByteArray = Xof.Utils.leftEncode(value) + override fun rightEncode(): ByteArray = Xof.Utils.rightEncode(value) + } + class LoHi(val lo: Int, val hi: Int): Data() { + override fun leftEncode(): ByteArray = Xof.Utils.leftEncode(lo = lo, hi = hi) + override fun rightEncode(): ByteArray = Xof.Utils.rightEncode(lo = lo, hi = hi) + } + + abstract fun leftEncode(): ByteArray + abstract fun rightEncode(): ByteArray + } + @Test fun givenValue_whenEncoded_thenIsAsExpected() { listOf( - 777711L to byteArrayOf(3, 11, -35, -17), - -777711L to byteArrayOf(8, -1, -1, -1, -1, -1, -12, 34, 17), - 555L to byteArrayOf(2, 2, 43), - Long.MIN_VALUE to byteArrayOf(8, -128, 0, 0, 0, 0, 0, 0, 0), - Long.MAX_VALUE to byteArrayOf(8, 127, -1, -1, -1, -1, -1, -1, -1), - ).forEach { (value, expected) -> - assertContentEquals(expected, Xof.Utils.leftEncode(value)) + Data.L(777711L) to byteArrayOf(3, 11, -35, -17), + Data.L(-777711L) to byteArrayOf(8, -1, -1, -1, -1, -1, -12, 34, 17), + Data.LoHi(-777711, -1) to byteArrayOf(8, -1, -1, -1, -1, -1, -12, 34, 17), + Data.L(555L) to byteArrayOf(2, 2, 43), + Data.I(555) to byteArrayOf(2, 2, 43), + Data.L(Long.MIN_VALUE) to byteArrayOf(8, -128, 0, 0, 0, 0, 0, 0, 0), + Data.L(Long.MAX_VALUE) to byteArrayOf(8, 127, -1, -1, -1, -1, -1, -1, -1), + ).forEach { (data, expected) -> + assertContentEquals(expected, data.leftEncode()) // Shift expected for right encoding var i = 0 @@ -43,7 +63,7 @@ class XofUtilsUnitTest { i++ } - assertContentEquals(expected, Xof.Utils.rightEncode(value)) + assertContentEquals(expected, data.rightEncode()) } } From 63a9e615c6810a1352201b25bf1c4016be6fce44 Mon Sep 17 00:00:00 2001 From: Matthew Nelson Date: Thu, 2 Jan 2025 17:08:31 -0500 Subject: [PATCH 4/5] Update benchmarks --- benchmarks/build.gradle.kts | 1 - .../org/kotlincrypto/core/benchmarks/XofOps.kt | 12 ++++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/benchmarks/build.gradle.kts b/benchmarks/build.gradle.kts index 9fee071..e509eed 100644 --- a/benchmarks/build.gradle.kts +++ b/benchmarks/build.gradle.kts @@ -54,7 +54,6 @@ kmpConfiguration { sourceSetMain { dependencies { implementation(libs.benchmark.runtime) - implementation(libs.kotlincrypto.endians.endians) implementation(project(":library:digest")) implementation(project(":library:xof")) } diff --git a/benchmarks/src/commonMain/kotlin/org/kotlincrypto/core/benchmarks/XofOps.kt b/benchmarks/src/commonMain/kotlin/org/kotlincrypto/core/benchmarks/XofOps.kt index 3aa4200..2d538d1 100644 --- a/benchmarks/src/commonMain/kotlin/org/kotlincrypto/core/benchmarks/XofOps.kt +++ b/benchmarks/src/commonMain/kotlin/org/kotlincrypto/core/benchmarks/XofOps.kt @@ -28,10 +28,18 @@ import kotlin.random.Random @OptIn(InternalKotlinCryptoApi::class) open class XofUtilsBenchmark { - private val longs = LongArray(10) { Random.Default.nextLong() } + private val longs: LongArray = LongArray(10) { Random.Default.nextLong() } + private val loHi = Array(longs.size) { longs[it].let { l -> l.toInt() to l.rotateLeft(32).toInt() } } @Benchmark - fun leftEncode() { + fun leftEncodeLongs() { + val longs = longs longs.forEach { long -> Xof.Utils.leftEncode(long) } } + + @Benchmark + fun leftEncodeLoHi() { + val loHi = loHi + loHi.forEach { Xof.Utils.leftEncode(it.first, it.second) } + } } From bf298cbfcf2842182882d13bc4485bd1ffcda61c Mon Sep 17 00:00:00 2001 From: Matthew Nelson Date: Thu, 2 Jan 2025 17:08:43 -0500 Subject: [PATCH 5/5] Remove endians dependency --- README.md | 3 --- gradle/libs.versions.toml | 4 ---- library/xof/build.gradle.kts | 1 - library/xof/src/jvmMain/java9/module-info.java | 1 - 4 files changed, 9 deletions(-) diff --git a/README.md b/README.md index 6d60f1a..6cdced5 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ [![badge-latest-release]][url-latest-release] [![badge-kotlin]][url-kotlin] -[![badge-endians]][url-endians] ![badge-platform-android] ![badge-platform-jvm] @@ -242,7 +241,6 @@ dependencies { [badge-kotlin]: https://img.shields.io/badge/kotlin-1.9.24-blue.svg?logo=kotlin -[badge-endians]: https://img.shields.io/badge/kotlincrypto.endians-0.3.1-blue.svg [badge-platform-android]: http://img.shields.io/badge/-android-6EDB8D.svg?style=flat @@ -266,7 +264,6 @@ dependencies { [url-license]: https://www.apache.org/licenses/LICENSE-2.0.txt [url-kotlin]: https://kotlinlang.org [url-kotlin-crypto]: https://github.com/KotlinCrypto -[url-endians]: https://github.com/KotlinCrypto/endians [url-hash]: https://github.com/KotlinCrypto/hash [url-macs]: https://github.com/KotlinCrypto/MACs [url-version-catalog]: https://github.com/KotlinCrypto/version-catalog diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4ca7a7c..e647649 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,15 +8,11 @@ gradle-kmp-configuration = "0.3.2" gradle-kotlin = "1.9.24" gradle-publish-maven = "0.29.0" -kotlincrypto-endians = "0.3.1" - [libraries] gradle-kmp-configuration = { module = "io.matthewnelson:gradle-kmp-configuration-plugin", version.ref = "gradle-kmp-configuration" } gradle-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "gradle-kotlin" } gradle-publish-maven = { module = "com.vanniktech:gradle-maven-publish-plugin", version.ref = "gradle-publish-maven" } -kotlincrypto-endians-endians = { module = "org.kotlincrypto.endians:endians", version.ref = "kotlincrypto-endians" } - # tests & tools androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidx-test-runner" } benchmark-runtime = { module = "org.jetbrains.kotlinx:kotlinx-benchmark-runtime", version.ref = "gradle-benchmark" } diff --git a/library/xof/build.gradle.kts b/library/xof/build.gradle.kts index 333e7e4..0d68bbc 100644 --- a/library/xof/build.gradle.kts +++ b/library/xof/build.gradle.kts @@ -22,7 +22,6 @@ kmpConfiguration { common { sourceSetMain { dependencies { - implementation(libs.kotlincrypto.endians.endians) api(project(":library:core")) } } diff --git a/library/xof/src/jvmMain/java9/module-info.java b/library/xof/src/jvmMain/java9/module-info.java index 12f0e09..bfe2b22 100644 --- a/library/xof/src/jvmMain/java9/module-info.java +++ b/library/xof/src/jvmMain/java9/module-info.java @@ -1,7 +1,6 @@ module org.kotlincrypto.core.xof { requires kotlin.stdlib; requires transitive org.kotlincrypto.core; - requires org.kotlincrypto.endians; exports org.kotlincrypto.core.xof; }