From 4142549d2e85d768000d1fdfd74d193e6e622b63 Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Sat, 23 May 2020 16:18:12 +0200 Subject: [PATCH] Further reduction in allocations --- .../crypto/keyderivation/argon2/Argon2.kt | 74 +++-- .../keyderivation/argon2/Argon2Matrix.kt | 233 --------------- .../keyderivation/argon2/Argon2Utils.kt | 58 ++-- .../keyderivation/argon2/ArgonMatrix.kt | 279 ++++++++++++++++++ .../crypto/hash/argon/Argon2MatrixTest.kt | 54 +++- 5 files changed, 381 insertions(+), 317 deletions(-) delete mode 100644 multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2Matrix.kt create mode 100644 multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/ArgonMatrix.kt 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 bf68f10..f4ca818 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 @@ -24,6 +24,7 @@ import com.ionspin.kotlin.crypto.hash.blake2b.Blake2b import com.ionspin.kotlin.crypto.keyderivation.KeyDerivationFunction import com.ionspin.kotlin.crypto.keyderivation.argon2.Argon2Utils.argonBlake2bArbitraryLenghtHash import com.ionspin.kotlin.crypto.keyderivation.argon2.Argon2Utils.compressionFunctionG +import com.ionspin.kotlin.crypto.keyderivation.argon2.Argon2Utils.inplaceCompressionFunctionG import com.ionspin.kotlin.crypto.keyderivation.argon2.Argon2Utils.validateArgonParameters import com.ionspin.kotlin.crypto.util.* @@ -133,10 +134,10 @@ class Argon2( private val useIndependentAddressing = argonType == ArgonType.Argon2id || argonType == ArgonType.Argon2i // State - private val matrix: Argon2Matrix + private val matrix: ArgonMatrix init { - matrix = Argon2Matrix(columnCount, parallelism) + matrix = ArgonMatrix(columnCount, parallelism) validateArgonParameters( password, salt, @@ -156,25 +157,27 @@ class Argon2( iteration: Int, slice: Int, lane: Int, - addressBlock: UByteArray, + addressBlock: ArgonBlockPointer, addressCounter: ULong - ): UByteArray { + ): ArgonBlockPointer { //Calculate first pass - val firstPass = compressionFunctionG( - UByteArray(1024) { 0U }, - iteration.toULong().toLittleEndianUByteArray() + + val zeroesBlock = ArgonBlock() + val firstPass = inplaceCompressionFunctionG( + zeroesBlock.getBlockPointer(), + ArgonBlock(iteration.toULong().toLittleEndianUByteArray() + lane.toULong().toLittleEndianUByteArray() + slice.toULong().toLittleEndianUByteArray() + blockCount.toULong().toLittleEndianUByteArray() + numberOfIterations.toULong().toLittleEndianUByteArray() + argonType.typeId.toULong().toLittleEndianUByteArray() + addressCounter.toLittleEndianUByteArray() + - UByteArray(968) { 0U }, + UByteArray(968) { 0U } + ).getBlockPointer(), addressBlock, false ) - val secondPass = compressionFunctionG( - UByteArray(1024) { 0U }, + val secondPass = inplaceCompressionFunctionG( + zeroesBlock.getBlockPointer(), firstPass, firstPass, false @@ -188,46 +191,41 @@ class Argon2( slice: Int, lane: Int, column: Int, - addressBlock: UByteArray? + addressBlockPointer: ArgonBlockPointer? ): Pair { val segmentIndex = (column % segmentLength) val independentIndex = segmentIndex % 128 // 128 is the number of addresses in address block val (j1, j2) = when (argonType) { ArgonType.Argon2d -> { - val (previousBlockStart, previousBlockEnd) = if (column == 0) { - matrix.getBlockStartAndEndPositions(lane, columnCount - 1) //Get last block in the SAME lane + val previousBlockStart = if (column == 0) { + matrix.getBlockPointer(lane, columnCount - 1) //Get last block in the SAME lane } else { - matrix.getBlockStartAndEndPositions(lane, column - 1) + matrix.getBlockPointer(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() + val first32Bit = matrix.sliceArray(previousBlockStart.asInt() until previousBlockStart.asInt() + 4).fromLittleEndianArrayToUInt() + val second32Bit = matrix.sliceArray(previousBlockStart.asInt() + 4 until previousBlockStart.asInt() + 8).fromLittleEndianArrayToUInt() Pair(first32Bit, second32Bit) } ArgonType.Argon2i -> { - val selectedAddressBlock = - addressBlock!!.sliceArray((independentIndex * 8) until (independentIndex * 8) + 8) - val first32Bit = selectedAddressBlock.sliceArray(0 until 4).fromLittleEndianArrayToUInt() - val second32Bit = selectedAddressBlock.sliceArray(4 until 8).fromLittleEndianArrayToUInt() + val first32Bit = addressBlockPointer!!.getUIntFromPosition(independentIndex * 8) + val second32Bit = addressBlockPointer!!.getUIntFromPosition(independentIndex * 8 + 4) Pair(first32Bit, second32Bit) } ArgonType.Argon2id -> { if (iteration == 0 && (slice == 0 || slice == 1)) { - val selectedAddressBlock = - addressBlock!!.sliceArray((independentIndex * 8) until (independentIndex * 8) + 8) - val first32Bit = selectedAddressBlock.sliceArray(0 until 4).fromLittleEndianArrayToUInt() - val second32Bit = selectedAddressBlock.sliceArray(4 until 8).fromLittleEndianArrayToUInt() + val first32Bit = addressBlockPointer!!.getUIntFromPosition(independentIndex * 8) + val second32Bit = addressBlockPointer!!.getUIntFromPosition(independentIndex * 8 + 4) Pair(first32Bit, second32Bit) } else { - val (previousBlockStart, previousBlockEnd) = if (column == 0) { - matrix.getBlockStartAndEndPositions(lane, columnCount - 1) //Get last block in the SAME lane + val previousBlockStart = if (column == 0) { + matrix.getBlockPointer(lane, columnCount - 1) //Get last block in the SAME lane } else { - matrix.getBlockStartAndEndPositions(lane, column - 1) + matrix.getBlockPointer(lane, column - 1) } - val first32Bit = matrix.sliceArray(previousBlockStart until previousBlockStart + 4).fromLittleEndianArrayToUInt() - val second32Bit = matrix.sliceArray(previousBlockStart + 4 until previousBlockStart + 8).fromLittleEndianArrayToUInt() + val first32Bit = matrix.sliceArray(previousBlockStart.asInt() until previousBlockStart.asInt() + 4).fromLittleEndianArrayToUInt() + val second32Bit = matrix.sliceArray(previousBlockStart.asInt() + 4 until previousBlockStart.asInt() + 8).fromLittleEndianArrayToUInt() Pair(first32Bit, second32Bit) } @@ -356,14 +354,12 @@ class Argon2( val slice = segmentPosition.slice val lane = segmentPosition.lane - var addressBlock: UByteArray? = null + var addressBlock: ArgonBlockPointer? = null var addressCounter = 1UL //Starts from 1 in each segment as defined by the spec //Generate initial segment address block if (useIndependentAddressing) { - addressBlock = UByteArray(1024) { - 0U - } + addressBlock = ArgonBlock().getBlockPointer() addressBlock = populateAddressBlock(iteration, slice, lane, addressBlock, addressCounter) addressCounter++ } @@ -396,12 +392,12 @@ class Argon2( ) matrix.setBlockAt(lane, column, - compressionFunctionG( - matrix.getBlockAt(lane, previousColumn), - matrix.getBlockAt(l,z), - matrix.getBlockAt(lane,column), + inplaceCompressionFunctionG( + matrix.getBlockPointer(lane, previousColumn), + matrix.getBlockPointer(l,z), + matrix.getBlockPointer(lane,column), true - ).toUByteArray() + ).getAsUByteArray() ) } 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 deleted file mode 100644 index cc7d70f..0000000 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2Matrix.kt +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright 2019 Ugljesa Jovanovic - * - * 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 - * - * http://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("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") - -package com.ionspin.kotlin.crypto.keyderivation.argon2 - -/** - * Created by Ugljesa Jovanovic - * ugljesa.jovanovic@ionspin.com - * on 21-May-2020 - */ - class Argon2Matrix(val columnCount: Int, val rowCount: Int) { - - internal val storage: UByteArray = UByteArray(columnCount * rowCount * 1024) - - operator fun get(rowPosition: Int, columnPosition: Int, inBlockPosition: Int) : UByte { - if (rowPosition > rowCount - 1) { - throw RuntimeException("Invalid row (lane) requested: $rowPosition, rowCount: $rowCount") - } - if (columnPosition > columnCount - 1) { - throw RuntimeException("Invalid column requested: $columnPosition, columnCount: $columnCount") - } - 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[getBlockStartPositionPointer(rowPosition, columnPosition).intStorage + inBlockPosition] = value - } - - fun getBlockStartAndEndPositions(rowPosition: Int, columnPosition: Int) : Pair { - val start = getBlockStartPositionPointer(rowPosition, columnPosition) - return Pair( - start, - start + 1024 - ) - } - - fun sliceArray(indices: IntRange): UByteArray { - return storage.sliceArray(indices) - } - - fun getBlockAt(rowPosition: Int, columnPosition: Int) : UByteArray { - println("Expensive get") - return storage.copyOfRange( - getBlockStartPositionPointer(rowPosition, columnPosition).intStorage, - getBlockStartPositionPointer(rowPosition, columnPosition).intStorage + 1024 - ) - } - - fun setBlockAt(rowPosition: Int, columnPosition: Int, blockValue: UByteArray) { - println("Expensive set") - blockValue.copyInto( - storage, - getBlockStartPositionPointer(rowPosition, columnPosition).intStorage - ) - } - - 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 { -// println("Expensive.") -// return storage.copyOfRange( -// rowPosition * (columnCount - 1) * 1024 + columnPosition * 1024, -// rowPosition * (columnCount - 1) * 1024 + columnPosition * 1024 + 1024 -// ) -// } -// - - - 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 84c9cdc..76aabaf 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 @@ -111,38 +111,30 @@ 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 inplaceCompressionFunctionG( + previousBlock: ArgonBlockPointer, + referenceBlock: ArgonBlockPointer, + currentBlock: ArgonBlockPointer, + xorWithCurrentBlock: Boolean + ): ArgonBlockPointer { + val r = (referenceBlock xorBlocksAndGetPointerToNewBlock previousBlock).getBlockPointer() + val q = ArgonBlock().getBlockPointer() + val z = ArgonBlock().getBlockPointer() + // 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) { + z.setColumnFromMixedULongs(i, inplaceMixRound(q.getColumnOfULongsForMixing(i))) + } + val final = if (xorWithCurrentBlock) { + (z xorInplace r) xorInplace currentBlock + } else { + z xorInplace r + } + return final + } internal fun compressionFunctionG( previousBlock: UByteArray, @@ -249,6 +241,6 @@ object Argon2Utils { // ------------ Arithmetic and other utils @ExperimentalUnsignedTypes -fun UByteArray.xorWithBlock(other : Argon2Matrix, rowPosition: Int, columnPosition: Int) : UByteArray { +fun UByteArray.xorWithBlock(other : ArgonMatrix, rowPosition: Int, columnPosition: Int) : UByteArray { return UByteArray(BLOCK_SIZE) { this[it] xor other[rowPosition, columnPosition, it] } } \ No newline at end of file diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/ArgonMatrix.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/ArgonMatrix.kt new file mode 100644 index 0000000..0cf4d6c --- /dev/null +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/ArgonMatrix.kt @@ -0,0 +1,279 @@ +/* + * Copyright 2019 Ugljesa Jovanovic + * + * 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 + * + * http://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("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") + +package com.ionspin.kotlin.crypto.keyderivation.argon2 + +/** + * Represents a pointer to a Argon2 Block, this abstracts what the backing structure is, a Argon 2 Matrix or + * or a UByteArray block + */ +interface ArgonBlockPointer { + val blockStartPosition: Int + + fun pointerArithmetic(block : (Int, Int) -> Int) : ArgonBlockPointer + + infix fun xorBlocksAndGetPointerToNewBlock(other: ArgonBlockPointer) : ArgonBlock + + operator fun get(blockPosition: Int) : UByte + + operator fun set(blockPosition: Int, value: UByte) + + infix fun xorInplace(other: ArgonBlockPointer) : ArgonBlockPointer { + (0 until 1024).forEach { + this[it] = this[it] xor other[it] + } + return this //For chaining + } + + fun asInt() : Int + + fun getAsUByteArray() : UByteArray +} + +fun ArgonBlockPointer.getRowOfULongsForMixing(rowIndex: Int) : ULongArray { + // 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 (columnIndex in 0 until 16) { + var ulong = 0UL + //Now we create the ulong + for (bytePosition in 0 until 8) { + ulong = ulong or (this[rowIndex * 128 + columnIndex * 8 + bytePosition].toULong() shl (bytePosition * 8)) + } + ulongArray[columnIndex] = ulong + } + return ulongArray +} + +fun ArgonBlockPointer.setRowFromMixedULongs(rowIndex: Int, ulongs: ULongArray) { + // 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 (columnIndex in 0 until 16) { + val ulongToConvert = ulongs[columnIndex] + for (bytePosition in 0 until 8) { + this[rowIndex * 128 + columnIndex * 8 + bytePosition] = ((ulongToConvert shr (bytePosition * 8)) and 0xFFU).toUByte() + } + } +} + +fun ArgonBlockPointer.getColumnOfULongsForMixing(columnIndex: Int) : ULongArray { + //In Argon2 representation there are 8 double word registers (numbers, but we work with 16 single word ulongs + val ulongArray = ULongArray(16) + //There are 8 rows that consist of 2 words (registers, in our case ulongs) each + for (rowIndex in 0 until 8) { + var ulong = 0UL + //Now we create the ulong + for (bytePosition in 0 until 8) { + ulong = ulong or (this[rowIndex * 128 + columnIndex * 16 + bytePosition].toULong() shl (bytePosition * 8)) + } + ulongArray[rowIndex * 2] = ulong + ulong = 0UL + // But unlike in columns where we can directly iterate and get all TWO WORD registers, here we also need to grab + // the next word + for (bytePosition in 8 until 16) { + ulong = ulong or (this[rowIndex * 128 + columnIndex * 16 + bytePosition].toULong() shl (bytePosition * 8)) + } + ulongArray[rowIndex * 2 + 1] = ulong + } + return ulongArray +} + +fun ArgonBlockPointer.setColumnFromMixedULongs(columnIndex: Int, ulongs: ULongArray) { + //In Argon2 representation there are 8 double word registers (numbers, but we work with 16 single word ulongs + //There are 8 rows that consist of 2 words (registers, in our case ulongs) each + var ulongToConvert = 0UL + for (rowIndex in 0 until 8) { + ulongToConvert = ulongs[rowIndex * 2] + //Now we create the ulong + for (bytePosition in 0 until 8) { + this[rowIndex * 128 + columnIndex * 16 + bytePosition] = ((ulongToConvert shr (bytePosition * 8)) and 0xFFU).toUByte() + } + // But unlike in columns where we can directly iterate and get all TWO WORD registers, here we also need to set + // the next word + ulongToConvert = ulongs[rowIndex * 2 + 1] + for (bytePosition in 8 until 16) { + this[rowIndex * 128 + columnIndex * 16 + bytePosition] = ((ulongToConvert shr (bytePosition * 8)) and 0xFFU).toUByte() + } + } +} + +fun ArgonBlockPointer.getUIntFromPosition(positionInBlock: Int) : UInt { + var uint = 0U + for (i in 0 until 4) { + uint = uint or (this[positionInBlock + i].toUInt() shl (i * 8)) + } + return uint +} + +/** + * Created by Ugljesa Jovanovic + * ugljesa.jovanovic@ionspin.com + * on 21-May-2020 + */ + class ArgonMatrix(val columnCount: Int, val rowCount: Int) { + + internal val storage: UByteArray = UByteArray(columnCount * rowCount * 1024) + + operator fun get(rowPosition: Int, columnPosition: Int, inBlockPosition: Int) : UByte { + if (rowPosition > rowCount - 1) { + throw RuntimeException("Invalid row (lane) requested: $rowPosition, rowCount: $rowCount") + } + if (columnPosition > columnCount - 1) { + throw RuntimeException("Invalid column requested: $columnPosition, columnCount: $columnCount") + } + return storage[getBlockStartPositionPointer(rowPosition, columnPosition) + inBlockPosition] + } + + val size = storage.size + + operator fun get(absolutePosition: Int) : UByte { + return storage[absolutePosition] + } + + operator fun set(rowPosition: Int, columnPosition: Int, inBlockPosition: Int, value: UByte) { + storage[getBlockStartPositionPointer(rowPosition, columnPosition) + inBlockPosition] = value + } + + operator fun set(absolutePosition: Int, value: UByte) { + storage[absolutePosition] = value + } + + fun getBlockPointer(rowPosition: Int, columnPosition: Int) : ArgonBlockPointer { + return ArgonBlockPointerWithMatrix(getBlockStartPositionPointer(rowPosition, columnPosition), this) + } + + + fun sliceArray(indices: IntRange): UByteArray { + return storage.sliceArray(indices) + } + + fun getBlockAt(rowPosition: Int, columnPosition: Int) : UByteArray { + println("Expensive get") + return storage.copyOfRange( + getBlockStartPositionPointer(rowPosition, columnPosition), + getBlockStartPositionPointer(rowPosition, columnPosition) + 1024 + ) + } + + fun setBlockAt(rowPosition: Int, columnPosition: Int, blockValue: UByteArray) { + println("Expensive set") + blockValue.copyInto( + storage, + getBlockStartPositionPointer(rowPosition, columnPosition) + ) + } + + private inline fun getBlockStartPositionPointer(rowPosition: Int, columnPosition: Int) : Int { + return rowPosition * columnCount * 1024 + columnPosition * 1024 + } + + internal fun clearMatrix() { + for( index in storage.indices) { storage[index] = 0U } + } + + private class ArgonBlockPointerWithMatrix constructor(override val blockStartPosition: Int, val matrix: ArgonMatrix) : ArgonBlockPointer { + + override fun pointerArithmetic(block: (Int, Int) -> Int): ArgonBlockPointer { + return ArgonBlockPointerWithMatrix(block(blockStartPosition, matrix.size), matrix) + } + + override fun asInt(): Int { + return blockStartPosition + } + + override operator fun get(blockPosition: Int) : UByte { + return matrix[blockStartPosition + blockPosition] + } + + override fun set(blockPosition: Int, value: UByte) { + matrix[blockStartPosition + blockPosition] = value + } + + override infix fun xorBlocksAndGetPointerToNewBlock(other: ArgonBlockPointer) : ArgonBlock { + return ArgonBlock(UByteArray(1024){ + matrix[blockStartPosition + it] xor other[it] + }) + } + + override fun getAsUByteArray(): UByteArray { + return matrix.storage.slice(blockStartPosition until blockStartPosition + 1024).toUByteArray() + } + } +} + + + + +@Suppress("NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS") +inline class ArgonBlock internal constructor(internal val storage: UByteArray) { + 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) + } + + val size: Int get() = storage.size + + internal fun getAsUByteArray() : UByteArray = storage + + fun getBlockPointer() : ArgonBlockPointer{ + return ArgonBlockPointerWithBlock( this) + } + + infix fun xorInplace(other: ArgonBlock) : ArgonBlock { + storage.indices.forEach { + this[it] = this[it] xor other[it] + } + return this //For chaining + } + + + + private class ArgonBlockPointerWithBlock constructor(val storageBlock: ArgonBlock) : ArgonBlockPointer { + override val blockStartPosition: Int = 0 + + override fun pointerArithmetic(block: (Int, Int) -> Int): ArgonBlockPointer { + throw RuntimeException("Haven't really tought out pointer arithmetic with blocks") + } + + override fun asInt(): Int { + return blockStartPosition + } + override operator fun get(blockPosition: Int) : UByte { + return storageBlock[blockPosition] + } + + override fun set(blockPosition: Int, value: UByte) { + storageBlock[blockPosition] = value + } + + override infix fun xorBlocksAndGetPointerToNewBlock(other: ArgonBlockPointer) : ArgonBlock { + return ArgonBlock(UByteArray(1024){ + storageBlock[it] xor other[it] + }) + } + + override fun getAsUByteArray(): UByteArray { + return storageBlock.storage.slice(blockStartPosition until blockStartPosition + 1024).toUByteArray() + } + + } + +} \ No newline at end of file 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 9f801db..ad69c98 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 @@ -18,9 +18,7 @@ package com.ionspin.kotlin.crypto.hash.argon -import com.ionspin.kotlin.crypto.keyderivation.argon2.Argon2Matrix -import com.ionspin.kotlin.crypto.keyderivation.argon2.Argon2Utils -import com.ionspin.kotlin.crypto.keyderivation.argon2.Block +import com.ionspin.kotlin.crypto.keyderivation.argon2.* import com.ionspin.kotlin.crypto.util.arrayChunked import com.ionspin.kotlin.crypto.util.fromLittleEndianArrayToULong import kotlin.random.Random @@ -45,7 +43,7 @@ class Argon2MatrixTest { @Test fun indexAccessTest() { - val argon2Matrix = Argon2Matrix(2, 2) + val argon2Matrix = ArgonMatrix(2, 2) (zeroesBlock + onesBlock + twosBlock + threesBlock).copyInto(argon2Matrix.storage) println(argon2Matrix[0, 0, 0]) println(argon2Matrix[0, 1, 0]) @@ -74,7 +72,7 @@ class Argon2MatrixTest { @Test fun blockRetrievalTest() { - val argon2Matrix = Argon2Matrix(2, 2) + val argon2Matrix = ArgonMatrix(2, 2) (zeroesBlock + onesBlock + twosBlock + threesBlock).copyInto(argon2Matrix.storage) assertTrue { zeroesBlock.contentEquals(argon2Matrix.getBlockAt(0, 0)) && @@ -86,14 +84,14 @@ class Argon2MatrixTest { @Test fun blockColumnToUlongTest() { - val randomBlock = Block(randomBlockArray) + val randomBlock = ArgonBlock(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) + val result = randomBlock.getBlockPointer().getRowOfULongsForMixing(columnIndex) assertTrue { expected.contentEquals(result) } } @@ -101,12 +99,44 @@ class Argon2MatrixTest { @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) + val randomBlock = ArgonBlock(randomBlockArray) + for (rowIndex in 0 until 8) { + val columnToMix = Argon2Utils.extractColumnFromGBlock(randomBlockArray, rowIndex) + val expected = columnToMix.arrayChunked(8).map { it.fromLittleEndianArrayToULong() }.toULongArray() + val result = randomBlock.getBlockPointer().getColumnOfULongsForMixing(rowIndex) - assertTrue { expected.contentEquals(result) } + assertTrue { expected.contentEquals(result) } + } + } + + @Test + fun blockSetMixedRowTest() { + val randomBlock = ArgonBlock(randomBlockArray) + val targetBlockArray = zeroesBlock.copyOf() + val targetBlock = ArgonBlock(targetBlockArray) + for (rowIndex in 0 until 8) { + val extracted = randomBlock.getBlockPointer().getRowOfULongsForMixing(rowIndex) + targetBlock.getBlockPointer().setRowFromMixedULongs(rowIndex, extracted) + } + + assertTrue { + randomBlockArray.contentEquals(targetBlock.storage) + } + } + + @Test + fun blockSetMixedColumnTest() { + val randomBlock = ArgonBlock(randomBlockArray) + val targetBlockArray = zeroesBlock.copyOf() + val targetBlock = ArgonBlock(targetBlockArray) + for (columnIndex in 0 until 8) { + val extracted = randomBlock.getBlockPointer().getColumnOfULongsForMixing(columnIndex) + targetBlock.getBlockPointer().setColumnFromMixedULongs(columnIndex, extracted) + } + + assertTrue { + randomBlockArray.contentEquals(targetBlock.storage) + } } } \ No newline at end of file