From 3902b90b57de600ea8464780412a87262f0eb96d Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Sat, 23 May 2020 11:20:36 +0200 Subject: [PATCH] Allocation removal progress --- .../crypto/keyderivation/argon2/Argon2.kt | 5 +- .../keyderivation/argon2/Argon2Matrix.kt | 171 ++++++++++++++++-- .../keyderivation/argon2/Argon2Utils.kt | 57 ++++++ .../crypto/hash/argon/Argon2MatrixTest.kt | 36 +++- 4 files changed, 251 insertions(+), 18 deletions(-) diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2.kt index 3fcb9d0..bf68f10 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2.kt @@ -52,6 +52,8 @@ data class ArgonResult( } + + @ExperimentalStdlibApi class Argon2( private val password: UByteArray, @@ -198,6 +200,7 @@ class Argon2( } else { matrix.getBlockStartAndEndPositions(lane, column - 1) } + val bla = 1 until 3 val first32Bit = matrix.sliceArray(previousBlockStart until previousBlockStart + 4).fromLittleEndianArrayToUInt() val second32Bit = matrix.sliceArray(previousBlockStart + 4 until previousBlockStart + 8).fromLittleEndianArrayToUInt() @@ -325,7 +328,7 @@ class Argon2( //Temporary fold - val acc = matrix.getBlockAt(0, columnCount - 1).copyOf() + val acc = matrix.getBlockAt(0, columnCount - 1) for (i in 1 until parallelism) { (acc.xorWithBlock(matrix, i, columnCount - 1).copyInto(acc)) } diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2Matrix.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2Matrix.kt index 80b3523..cc7d70f 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2Matrix.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2Matrix.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -@file:Suppress("EXPERIMENTAL_API_USAGE") +@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") package com.ionspin.kotlin.crypto.keyderivation.argon2 @@ -34,15 +34,23 @@ package com.ionspin.kotlin.crypto.keyderivation.argon2 if (columnPosition > columnCount - 1) { throw RuntimeException("Invalid column requested: $columnPosition, columnCount: $columnCount") } - return storage[getBlockStartPosition(rowPosition, columnPosition) + inBlockPosition] + return storage[getBlockStartPositionPointer(rowPosition, columnPosition).intStorage + inBlockPosition] + } + + operator fun get(absolutePosition: Int) : UByte { + return storage[absolutePosition] + } + + operator fun get(absolutePosition: BlockPointer) : UByte { + return storage[absolutePosition.intStorage] } operator fun set(rowPosition: Int, columnPosition: Int, inBlockPosition: Int, value: UByte) { - storage[getBlockStartPosition(rowPosition, columnPosition) + inBlockPosition] = value + storage[getBlockStartPositionPointer(rowPosition, columnPosition).intStorage + inBlockPosition] = value } - fun getBlockStartAndEndPositions(rowPosition: Int, columnPosition: Int) : Pair { - val start = getBlockStartPosition(rowPosition, columnPosition) + fun getBlockStartAndEndPositions(rowPosition: Int, columnPosition: Int) : Pair { + val start = getBlockStartPositionPointer(rowPosition, columnPosition) return Pair( start, start + 1024 @@ -56,8 +64,8 @@ package com.ionspin.kotlin.crypto.keyderivation.argon2 fun getBlockAt(rowPosition: Int, columnPosition: Int) : UByteArray { println("Expensive get") return storage.copyOfRange( - getBlockStartPosition(rowPosition, columnPosition), - getBlockStartPosition(rowPosition, columnPosition) + 1024 + getBlockStartPositionPointer(rowPosition, columnPosition).intStorage, + getBlockStartPositionPointer(rowPosition, columnPosition).intStorage + 1024 ) } @@ -65,12 +73,12 @@ package com.ionspin.kotlin.crypto.keyderivation.argon2 println("Expensive set") blockValue.copyInto( storage, - getBlockStartPosition(rowPosition, columnPosition) + getBlockStartPositionPointer(rowPosition, columnPosition).intStorage ) } - private inline fun getBlockStartPosition(rowPosition: Int, columnPosition: Int) : Int { - return rowPosition * columnCount * 1024 + columnPosition * 1024 + private inline fun getBlockStartPositionPointer(rowPosition: Int, columnPosition: Int) : BlockPointer { + return BlockPointer(rowPosition * columnCount * 1024 + columnPosition * 1024, this) } // operator fun get(rowPosition: Int, columnPosition: Int) : UByteArray { @@ -81,14 +89,145 @@ package com.ionspin.kotlin.crypto.keyderivation.argon2 // ) // } // -// operator fun get(rowPosition: Int) : Array { -// return Array(columnCount) { -// this.get(rowPosition, it) -// } -// -// } + internal fun clearMatrix() { for( index in storage.indices) { storage[index] = 0U } } +} +//TODO Decide: inline class without matrix reference? +class BlockPointer(val intStorage: Int, val matrix: Argon2Matrix) { + operator fun rangeTo(other: BlockPointer): IntRange { + return intStorage.rangeTo(other.intStorage) + } + + infix fun until(to: BlockPointer): IntRange { + if (to.intStorage <= Int.MIN_VALUE) return IntRange.EMPTY + return this .. BlockPointer(to.intStorage - 1, matrix) + } + + inline operator fun plus(other: BlockPointer) : BlockPointer { + return BlockPointer(intStorage.plus(other.intStorage), matrix) + } + inline operator fun plus(other: Int) : BlockPointer { + return BlockPointer(intStorage.plus(other), matrix) + } + + inline operator fun minus(other: BlockPointer) : BlockPointer { + return BlockPointer(intStorage.minus(other.intStorage), matrix) + } + inline operator fun times(other: BlockPointer) : BlockPointer { + return BlockPointer(intStorage.times(other.intStorage), matrix) + } + inline operator fun div(other: BlockPointer) : BlockPointer { + return BlockPointer(intStorage.div(other.intStorage), matrix) + } + inline operator fun rem(other: BlockPointer) : BlockPointer { + return BlockPointer(intStorage.rem(other.intStorage), matrix) + } + + infix fun xorBlocks(other: BlockPointer) : Block { + return Block(UByteArray(1024){ + matrix[this.intStorage + it] xor other.matrix[other.intStorage + it] + }) + } + +} +@Suppress("NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS") +inline class Block internal constructor(val storage: UByteArray) : Collection { + constructor() : this(UByteArray(1024)) + operator fun get(index: Int) : UByte { + return storage.get(index) + } + operator fun set(index: Int, value: UByte) { + storage.set(index, value) + } + + override val size: Int get() = storage.size + + override operator fun iterator(): UByteIterator = Iterator(storage.toByteArray()) + + private class Iterator(private val array: ByteArray) : UByteIterator() { + private var index = 0 + override fun hasNext() = index < array.size + override fun nextUByte() = if (index < array.size) array[index++].toUByte() else throw NoSuchElementException(index.toString()) + } + //Taken from UByteArray implementation + override fun contains(element: UByte): Boolean { + // TODO: Eliminate this check after KT-30016 gets fixed. + // Currently JS BE does not generate special bridge method for this method. + if ((element as Any?) !is UByte) return false + + return storage.contains(element.toByte()) + } + + override fun containsAll(elements: Collection): Boolean { + return (elements as Collection<*>).all { it is UByte && storage.contains(it.toByte()) } + } + + override fun isEmpty(): Boolean = this.storage.size == 0 + + fun getRowOfULongsForMixing(rowIndex: Int) : ULongArray { + val startOfRow = (rowIndex * 8 * 16) + + // Each row has 16 unsigned longs (16 ulongs * 8 bytes = 128 bytes) -- Argon2 considers this as 2 word unsigned + // numbers, so strictly speaking argon representation is 8 * 8 matrix of 2 word unsigned numbers (registers + val ulongArray = ULongArray(16) + for (i in 0 until 16) { + var ulong = 0UL + //Now we create the ulong + for (j in 0 until 8) { + ulong = ulong or (storage[startOfRow + i * 8 + j].toULong() shl (j * 8)) + } + ulongArray[i] = ulong + } + return ulongArray + } + + /* + @ExperimentalUnsignedTypes + fun ULong.toLittleEndianUByteArray() :UByteArray { + return UByteArray (8) { + ((this shr (it * 8)) and 0xFFU).toUByte() + } + } + //copy into gblock column + for (i in 0..7) { + val column = columnData.copyOfRange(i * 16, i * 16 + 16) + column.copyInto(gBlock, i * 128 + columnPosition * 16) + } + */ + + fun setRowFromMixedULongs(rowIndex: Int, ulongs: ULongArray) { + val startOfRow = (rowIndex * 8 * 16) + // Each row has 16 unsigned longs (16 ulongs * 8 bytes = 128 bytes) -- Argon2 considers this as 2 word unsigned + // numbers, so strictly speaking argon representation is 8 * 8 matrix of 2 word unsigned numbers (registers + for (i in 0 until 16) { + val ulongToConvert = ulongs[i] + for (j in 0 until 8) { + storage[startOfRow + i * 8 + j] = ((ulongToConvert shr (j * 8)) and 0xFFU).toUByte() + } + } + } + /* + val result = UByteArray(128) { 0U } + for (i in 0..7) { + gBlock.copyOfRange(i * 128 + (columnPosition * 16), i * 128 + (columnPosition * 16) + 16) + .copyInto(result, i * 16) + } + return result + */ + fun getColumnOfULongsForMixing(columnIndex: Int) : ULongArray { + val ulongArray = ULongArray(16) + + for (i in 0 until 16) { + var ulong = 0UL + //Now we create the ulong + for (j in 0 until 8) { + ulong = ulong or (storage[i * 128 + columnIndex * 16 + j].toULong() shl (j * 8)) + } + } + return ulongArray + } + } \ No newline at end of file diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2Utils.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2Utils.kt index ebbad77..84c9cdc 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2Utils.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2Utils.kt @@ -40,6 +40,18 @@ object Argon2Utils { const val R3 = 16 const val R4 = 63 + internal fun inplaceMixRound(v : ULongArray) : ULongArray{ + mix(v, 0, 4, 8, 12) + mix(v, 1, 5, 9, 13) + mix(v, 2, 6, 10, 14) + mix(v, 3, 7, 11, 15) + mix(v, 0, 5, 10, 15) + mix(v, 1, 6, 11, 12) + mix(v, 2, 7, 8, 13) + mix(v, 3, 4, 9, 14) + return v //Just for chaining, array is already mixed + } + //based on Blake2b mixRound internal fun mixRound(input: UByteArray): Array { var v = input.arrayChunked(8).map { it.fromLittleEndianArrayToULong() }.toTypedArray() @@ -71,6 +83,18 @@ object Argon2Utils { return v } + //Based on Blake2b mix + private fun mix(v: ULongArray, a: Int, b: Int, c: Int, d: Int) { + v[a] = (v[a] + v[b] + 2U * (v[a] and 0xFFFFFFFFUL) * (v[b] and 0xFFFFFFFFUL)) + v[d] = (v[d] xor v[a]) rotateRight R1 + v[c] = (v[c] + v[d] + 2U * (v[c] and 0xFFFFFFFFUL) * (v[d] and 0xFFFFFFFFUL)) + v[b] = (v[b] xor v[c]) rotateRight R2 + v[a] = (v[a] + v[b] + 2U * (v[a] and 0xFFFFFFFFUL) * (v[b] and 0xFFFFFFFFUL)) + v[d] = (v[d] xor v[a]) rotateRight R3 + v[c] = (v[c] + v[d] + 2U * (v[c] and 0xFFFFFFFFUL) * (v[d] and 0xFFFFFFFFUL)) + v[b] = (v[b] xor v[c]) rotateRight R4 + } + internal fun extractColumnFromGBlock(gBlock: UByteArray, columnPosition: Int): UByteArray { val result = UByteArray(128) { 0U } for (i in 0..7) { @@ -87,6 +111,39 @@ object Argon2Utils { } } +// internal fun allocationlessCompressionFunctionG( +// matrix: Argon2Matrix, +// previousBlock: BlockPointer, +// referenceBlock: BlockPointer, +// currentBlock: BlockPointer, +// xorWithCurrentBlock: Boolean +// ): UByteArray { +// val r = referenceBlock xorBlocks previousBlock +// val q = Block() +// val z = Block() +// // Do the argon/blake2b mixing on rows +// for (i in 0..7) { +// q.setRowFromMixedULongs(i, inplaceMixRound(r.getRowOfULongsForMixing(i))) +// } +// // Do the argon/blake2b mixing on columns +// for (i in 0..7) { +// copyIntoGBlockColumn( +// z, +// i, +// mixRound(extractColumnFromGBlock(q, i)) +// .map { it.toLittleEndianUByteArray() } +// .flatMap { it.asIterable() } +// .toUByteArray() +// ) +// } +// val final = if (xorWithCurrentBlock) { +// (z xor r) xor currentBlock +// } else { +// z xor r +// } +// return final +// } + internal fun compressionFunctionG( previousBlock: UByteArray, referenceBlock: UByteArray, diff --git a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/hash/argon/Argon2MatrixTest.kt b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/hash/argon/Argon2MatrixTest.kt index 3cdc432..9f801db 100644 --- a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/hash/argon/Argon2MatrixTest.kt +++ b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/hash/argon/Argon2MatrixTest.kt @@ -19,7 +19,12 @@ package com.ionspin.kotlin.crypto.hash.argon import com.ionspin.kotlin.crypto.keyderivation.argon2.Argon2Matrix -import com.ionspin.kotlin.crypto.util.hexColumsPrint +import com.ionspin.kotlin.crypto.keyderivation.argon2.Argon2Utils +import com.ionspin.kotlin.crypto.keyderivation.argon2.Block +import com.ionspin.kotlin.crypto.util.arrayChunked +import com.ionspin.kotlin.crypto.util.fromLittleEndianArrayToULong +import kotlin.random.Random +import kotlin.random.nextUBytes import kotlin.test.Test import kotlin.test.assertTrue @@ -34,6 +39,9 @@ class Argon2MatrixTest { val twosBlock = UByteArray(1024) { 2U } val threesBlock = UByteArray(1024) { 3U } + val random = Random(1) + val randomBlockArray = random.nextUBytes(1024) + @Test fun indexAccessTest() { @@ -75,4 +83,30 @@ class Argon2MatrixTest { threesBlock.contentEquals(argon2Matrix.getBlockAt(1, 1)) } } + + @Test + fun blockColumnToUlongTest() { + val randomBlock = Block(randomBlockArray) + for (columnIndex in 0 until 8) { + val startOfRow = (columnIndex * 8 * 16) + val endOfRow = startOfRow + (8 * 16) + val rowToMix = randomBlockArray.copyOfRange(startOfRow, endOfRow) + val expected = rowToMix.arrayChunked(8).map { it.fromLittleEndianArrayToULong() }.toULongArray() + + val result = randomBlock.getRowOfULongsForMixing(columnIndex) + + assertTrue { expected.contentEquals(result) } + } + } + + @Test + fun blockRowToULongTest() { + val randomBlock = Block(randomBlockArray) + val columnToMix = Argon2Utils.extractColumnFromGBlock(randomBlockArray, 0) + val expected = columnToMix.arrayChunked(8).map { it.fromLittleEndianArrayToULong() }.toULongArray() + val result = randomBlock.getColumnOfULongsForMixing(0) + + assertTrue { expected.contentEquals(result) } + } + } \ No newline at end of file