Skip to content

Commit

Permalink
Add BLAKE2b & BLAKE2s implementations
Browse files Browse the repository at this point in the history
  • Loading branch information
05nelsonm committed Jan 8, 2025
1 parent 73af67e commit 4ad3518
Show file tree
Hide file tree
Showing 32 changed files with 1,341 additions and 33 deletions.
1 change: 1 addition & 0 deletions benchmarks/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ kmpConfiguration {
implementation(libs.kotlincrypto.bitops.bits)
implementation(libs.kotlincrypto.bitops.endian)
implementation(libs.kotlincrypto.sponges.keccak)
implementation(project(":library:blake2"))
implementation(project(":library:md"))
implementation(project(":library:sha1"))
implementation(project(":library:sha2"))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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.
**/
@file:Suppress("ClassName", "unused")

package org.kotlincrypto.hash.benchmarks

import kotlinx.benchmark.*
import org.kotlincrypto.hash.blake2.BLAKE2b_512
import org.kotlincrypto.hash.blake2.BLAKE2s_256

@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(BenchmarkTimeUnit.NANOSECONDS)
@Warmup(iterations = ITERATIONS, time = TIME_WARMUP)
@Measurement(iterations = ITERATIONS, time = TIME_MEASURE)
open class BLAKE2b_512Benchmark: DigestBenchmarkBase(BLAKE2b_512())

@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(BenchmarkTimeUnit.NANOSECONDS)
@Warmup(iterations = ITERATIONS, time = TIME_WARMUP)
@Measurement(iterations = ITERATIONS, time = TIME_MEASURE)
open class BLAKE2s_256Benchmark: DigestBenchmarkBase(BLAKE2s_256())
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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.
**/
@file:Suppress("unused")

package org.kotlincrypto.hash.benchmarks

import kotlinx.benchmark.*
import org.kotlincrypto.hash.md.MD5

@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(BenchmarkTimeUnit.NANOSECONDS)
@Warmup(iterations = ITERATIONS, time = TIME_WARMUP)
@Measurement(iterations = ITERATIONS, time = TIME_MEASURE)
open class MD5Benchmark: DigestBenchmarkBase(MD5())
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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.
**/
@file:Suppress("unused")

package org.kotlincrypto.hash.benchmarks

import kotlinx.benchmark.*
import org.kotlincrypto.hash.sha1.SHA1

@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(SHA1())
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
@file:Suppress("ClassName")
@file:Suppress("ClassName", "unused")

package org.kotlincrypto.hash.benchmarks

import kotlinx.benchmark.*
import org.kotlincrypto.hash.md.MD5
import org.kotlincrypto.hash.sha1.SHA1
import org.kotlincrypto.hash.sha2.SHA256
import org.kotlincrypto.hash.sha2.SHA512

@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(BenchmarkTimeUnit.NANOSECONDS)
@Warmup(iterations = ITERATIONS, time = TIME_WARMUP)
@Measurement(iterations = ITERATIONS, time = TIME_MEASURE)
open class MD5Benchmark: DigestBenchmarkBase(MD5())

@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(SHA1())

@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(BenchmarkTimeUnit.NANOSECONDS)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
@file:Suppress("ClassName")
@file:Suppress("ClassName", "unused")

package org.kotlincrypto.hash.benchmarks

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
@file:Suppress("ClassName")
@file:Suppress("ClassName", "unused")

package org.kotlincrypto.hash.benchmarks

import kotlinx.benchmark.*
import org.kotlincrypto.hash.blake2.BLAKE2b_512
import org.kotlincrypto.hash.blake2.BLAKE2s_256
import org.kotlincrypto.hash.sha2.SHA256
import org.kotlincrypto.hash.sha2.SHA512
import org.kotlincrypto.hash.sha3.SHA3_256
Expand Down Expand Up @@ -52,3 +54,17 @@ open class SHA3_256Benchmark_BouncyCastle: BouncyCastleBenchmarkBase(kcDigest =
@Warmup(iterations = ITERATIONS, time = TIME_WARMUP)
@Measurement(iterations = ITERATIONS, time = TIME_MEASURE)
open class SHAKE128Benchmark_BouncyCastle: BouncyCastleBenchmarkBase(kcDigest = SHAKE128())

@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(BenchmarkTimeUnit.NANOSECONDS)
@Warmup(iterations = ITERATIONS, time = TIME_WARMUP)
@Measurement(iterations = ITERATIONS, time = TIME_MEASURE)
open class BLAKE2b_512Benchmark_BouncyCastle: BouncyCastleBenchmarkBase(kcDigest = BLAKE2b_512())

@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(BenchmarkTimeUnit.NANOSECONDS)
@Warmup(iterations = ITERATIONS, time = TIME_WARMUP)
@Measurement(iterations = ITERATIONS, time = TIME_MEASURE)
open class BLAKE2s_256Benchmark_BouncyCastle: BouncyCastleBenchmarkBase(kcDigest = BLAKE2s_256())
2 changes: 2 additions & 0 deletions library/blake2/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ kmpConfiguration {
sourceSetMain {
dependencies {
api(libs.kotlincrypto.core.digest)
implementation(libs.kotlincrypto.bitops.bits)
implementation(libs.kotlincrypto.bitops.endian)
}
}
sourceSetTest {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
@file:Suppress("FunctionName")

package org.kotlincrypto.hash.blake2

import org.kotlincrypto.bitops.bits.Counter
import org.kotlincrypto.bitops.endian.Endian.Little.lePackUnsafe
import org.kotlincrypto.hash.blake2.internal.*

/**
* BLAKE2b implementation
*
Expand All @@ -27,6 +33,11 @@ package org.kotlincrypto.hash.blake2
* */
public class BLAKE2b: BLAKE2Digest {

private val v: LongArray
private val h: LongArray
private var m: Bit64Message?
private val t: Counter.Bit64

/**
* Primary constructor for creating a new [BLAKE2b] instance
*
Expand All @@ -37,31 +48,172 @@ public class BLAKE2b: BLAKE2Digest {
* */
@Throws(IllegalArgumentException::class)
public constructor(bitStrength: Int): super(BLOCK_SIZE_B, bitStrength) {
TODO("Not yet implemented")
this.v = LongArray(16)
this.h = IV.copyOf()
this.h[0] = IV[0] xor (digestLength().toLong() or 16842752L)
this.m = null
this.t = Counter.Bit64(incrementBy = blockSize().toLong())
}

private constructor(other: BLAKE2b): super(other) {
TODO("Not yet implemented")
this.v = other.v.copyOf()
this.h = other.h.copyOf()
this.m = other.m?.copy()
this.t = other.t.copy()
}

public override fun copy(): BLAKE2b = BLAKE2b(this)

protected override fun compressProtected(input: ByteArray, offset: Int) {
TODO("Not yet implemented")
val m = m

if (m == null) {
// The Digest abstraction will call compressProtected whenever 1 block of
// input is available for processing. BLAKE2 requires a finalization byte
// to be set for the last compression. Because of this and not knowing if
// more input will be coming, compressions are always 1 block behind until
// digestProtected is called which will polish things off.
//
// This scenario could arise if the Digest is updated with 1 full block of
// bytes (compressProtected is called and that block would be processed
// with no finalization flag), then digest gets called without adding more
// input (so a bufPos of 0); the Digest abstraction's buffer would be empty,
// but the last block of input had already been processed!
this.m = Bit64Message(input, offset)
return
}

t.increment()
F(h = h, m = m, tLo = t.lo, tHi = t.hi, f = 0L)

// Populate m with this invocation's input to process next
m.populate(input, offset)
}

protected override fun digestProtected(buffer: ByteArray, offset: Int): ByteArray {
TODO("Not yet implemented")
protected override fun digestProtected(buf: ByteArray, bufPos: Int): ByteArray {
var m = m

if (m != null) {
// Have a buffered block that needs to be processed

if (bufPos == 0) {
// No additional input, buffered block is final.
t.increment()
} else {
// Process buffered block and update m with buf
compressProtected(buf, 0)
}
} else {
// compressProtected has not been called yet. Input
// in the buffer is all that has been collected.
m = Bit64Message(buf, 0)

// Set global m so that reset call after return will
// clear it out for us.
this.m = m
}

val (tLo, tHi) = t.final(additional = bufPos)

val h = h
F(h = h, m = m, tLo = tLo, tHi = tHi, f = -1L)

val out = ByteArray(digestLength())
val rem = out.size % Long.SIZE_BYTES
val outLimit = out.size - rem

var hPos = 0
var outPos = 0

// Chunk
while (outPos < outLimit) {
out.lePackUnsafe(h[hPos++], outPos)
outPos += Long.SIZE_BYTES
}

if (rem > 0) {
out.lePackUnsafe(h[hPos], outPos, startIndex = 0, endIndex = rem)
}

return out
}

@Suppress("KotlinRedundantDiagnosticSuppress", "NOTHING_TO_INLINE")
private inline fun F(h: LongArray, m: Bit64Message, tLo: Long, tHi: Long, f: Long) {
val iv = IV
val v = v

// Initialize local work vector v
h.copyInto(v)
iv.copyInto(v, h.size, 0, 4)
v[12] = tLo xor iv[4]
v[13] = tHi xor iv[5]
v[14] = f xor iv[6]
v[15] = iv[7]

// Cryptographic mixing
val s = SIGMA

for (i in 0..<ROUND_COUNT) {
G(v = v, a = 0, b = 4, c = 8, d = 12, x = m[s[i][ 0]], y = m[s[i][ 1]])
G(v = v, a = 1, b = 5, c = 9, d = 13, x = m[s[i][ 2]], y = m[s[i][ 3]])
G(v = v, a = 2, b = 6, c = 10, d = 14, x = m[s[i][ 4]], y = m[s[i][ 5]])
G(v = v, a = 3, b = 7, c = 11, d = 15, x = m[s[i][ 6]], y = m[s[i][ 7]])

G(v = v, a = 0, b = 5, c = 10, d = 15, x = m[s[i][ 8]], y = m[s[i][ 9]])
G(v = v, a = 1, b = 6, c = 11, d = 12, x = m[s[i][10]], y = m[s[i][11]])
G(v = v, a = 2, b = 7, c = 8, d = 13, x = m[s[i][12]], y = m[s[i][13]])
G(v = v, a = 3, b = 4, c = 9, d = 14, x = m[s[i][14]], y = m[s[i][15]])
}

// xor the two halves
for (i in 0..<8) {
h[i] = h[i] xor v[i] xor v[i + 8]
}
}

@Suppress("KotlinRedundantDiagnosticSuppress", "NOTHING_TO_INLINE")
private inline fun G(v: LongArray, a: Int, b: Int, c: Int, d: Int, x: Long, y: Long) {
var va = v[a]
var vb = v[b]
var vc = v[c]
var vd = v[d]

va = (va + vb + x)
vd = (vd xor va ).rotateRight(R1)
vc = (vc + vd )
vb = (vb xor vc ).rotateRight(R2)
va = (va + vb + y)
vd = (vd xor va ).rotateRight(R3)
vc = (vc + vd )
vb = (vb xor vc ).rotateRight(R4)

v[a] = va
v[b] = vb
v[c] = vc
v[d] = vd
}

protected override fun resetProtected() {
TODO("Not yet implemented")
v.fill(0)
IV.copyInto(h)
h[0] = IV[0] xor (digestLength().toLong() or 16842752L)
m?.fill()
m = null
t.reset()
}

private companion object {
private const val ROUND_COUNT = 12

private const val R1 = 32
private const val R2 = 24
private const val R3 = 16
private const val R4 = 63

private val IV = longArrayOf(
7640891576956012808, -4942790177534073029, 4354685564936845355, -6534734903238641935,
5840696475078001361, -7276294671716946913, 2270897969802886507, 6620516959819538809,
7640891576956012808L, -4942790177534073029L, 4354685564936845355L, -6534734903238641935L,
5840696475078001361L, -7276294671716946913L, 2270897969802886507L, 6620516959819538809L,
)
}
}
Loading

0 comments on commit 4ad3518

Please sign in to comment.