All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.org.kotlincrypto.hash.sha3.SHAKEDigest.kt Maven / Gradle / Ivy

/*
 * Copyright (c) 2023 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.hash.sha3

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

/**
 * Core abstraction for:
 *  - CSHAKE128
 *  - CSHAKE256
 *  - SHAKE128
 *  - SHAKE256
 *
 * https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-185.pdf
 * https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf
 *
 * Also see support libraries utilized:
 *  - https://github.com/KotlinCrypto/endians
 *  - https://github.com/KotlinCrypto/sponges
 *
 * @see [ParallelDigest]
 * @see [TupleDigest]
 * */
public sealed class SHAKEDigest: KeccakDigest, XofAlgorithm {

    private val initBlock: ByteArray?
    private var isReadingXof: Boolean
    private val xOfMode: Boolean

    protected constructor(
        N: ByteArray?,
        S: ByteArray?,
        xOfMode: Boolean,
        algorithm: String,
        blockSize: Int,
        digestLength: Int,
    ): super(algorithm, blockSize, digestLength, dsByteFromInput(N, S)) {
        this.xOfMode = xOfMode
        this.isReadingXof = false

        @OptIn(InternalKotlinCryptoApi::class)
        this.initBlock = if (N?.isNotEmpty() == true || S?.isNotEmpty() == true) {
            val nSize = N?.size ?: 0
            val sSize = S?.size ?: 0

            // Prepare encodings
            val bE = Xof.Utils.leftEncode(blockSize.toLong())
            val nE = Xof.Utils.leftEncode((nSize * 8L))
            val sE = Xof.Utils.leftEncode((sSize * 8L))

            val b = ByteArray(bE.size + nE.size + nSize + sE.size + sSize)

            bE.copyInto(b)
            nE.copyInto(b, bE.size)
            N?.copyInto(b, bE.size + nE.size)
            sE.copyInto(b, bE.size + nE.size + nSize)
            S?.copyInto(b, bE.size + nE.size + nSize + sE.size)

            b
        } else {
            null
        }

        initBlock?.bytepad()
    }

    protected constructor(state: DigestState, digest: SHAKEDigest): super(state, digest) {
        this.xOfMode = digest.xOfMode
        this.isReadingXof = digest.isReadingXof
        this.initBlock = digest.initBlock
    }

    protected final override fun extract(A: F1600, out: ByteArray, offset: Int, len: Int, bytesRead: Long): ByteArray {
        return if (xOfMode && !isReadingXof) {
            // 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)
            }
            isReadingXof = true
            return newOut
        } else {
            super.extract(A, out, offset, len, bytesRead)
        }
    }

    protected override fun resetDigest() {
        super.resetDigest()
        initBlock?.bytepad()

        // Don't want to ever reset isReadingXof b/c Xof mode
        // uses a copy of the digest in an isolated Reader environment.
        // The copy created for that Xof.Reader will never go back to
        // being updated, nor will it be copied again.
//        isReadingXof = false
    }

    private fun ByteArray.bytepad() {
        super.updateDigest(this, 0, size)

        val remainder = size % blockSize()

        // No padding is needed
        if (remainder == 0) return

        repeat(blockSize() - remainder) {
            super.updateDigest(0)
        }
    }

    @OptIn(InternalKotlinCryptoApi::class)
    public sealed class SHAKEXofFactory: XofFactory() {

        protected inner class SHAKEXof
        @Throws(IllegalArgumentException::class)
        constructor(delegate: A) : XofFactory.XofDelegate(delegate) {

            init {
                require(delegate.xOfMode) { "xOfMode must be true" }

                // Some algorithms for the SHA3 derived functions require encoding
                // the final output length before outputting data. While in XOF mode,
                // the arbitrary output length, L, is represented as 0.
                require(delegate.digestLength() == 0) { "digestLength must be 0" }
            }

            protected override fun newReader(delegateCopy: A): Xof.Reader {

                // Calling digest() will flush the copy's buffered input and
                // apply padding and final permutation(s). Because it is in
                // 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++])
                        )
                    }

                    A.fill(0)

                    f1600
                }

                return object : Reader() {
                    override fun readProtected(out: ByteArray, offset: Int, len: Int, bytesRead: Long) {
                        delegateCopy.extract(state, out, offset, len, bytesRead)
                    }

                    override fun closeProtected() {
                        // delegateCopy was already reset when digest()
                        // was called in order to pass state.

                        state.reset()
                    }
                }
            }

            @Suppress("UNCHECKED_CAST")
            public override fun copy(): Xof = SHAKEXof(delegate.copy() as A)
        }
    }

    protected companion object {
        internal const val SHAKE: String = "SHAKE"
        internal const val CSHAKE: String = "C$SHAKE"

        private const val PAD_SHAKE: Byte = 0x1f
        private const val PAD_CSHAKE: Byte = 0x04

        internal const val  BIT_STRENGTH_128 = 128
        internal const val  BIT_STRENGTH_256 = 256

        // blockSize for bitStrength of 128
        internal const val BLOCK_SIZE_BIT_128 = 168
        // blockSize for bitStrength of 256
        internal const val BLOCK_SIZE_BIT_256 = 136

        // default digestLength for bitStrength of 128
        internal const val DIGEST_LENGTH_BIT_128 = 32
        // default digestLength for bitStrength of 256
        internal const val DIGEST_LENGTH_BIT_256 = 64

        /**
         * Given that [N] and [S] are both null and/or empty, CSHAKE is
         * functionally equivalent to SHAKE and thus the [dsByte] must
         * reflect so.
         * */
        @JvmStatic
        private fun dsByteFromInput(N: ByteArray?, S: ByteArray?): Byte {
            return if (N?.isNotEmpty() == true || S?.isNotEmpty() == true) PAD_CSHAKE else PAD_SHAKE
        }

        @JvmSynthetic
        @Throws(IllegalArgumentException::class)
        internal fun blockSizeFromBitStrength(bitStrength: Int): Int {
            return when (bitStrength) {
                BIT_STRENGTH_128 -> BLOCK_SIZE_BIT_128
                BIT_STRENGTH_256 -> BLOCK_SIZE_BIT_256
                else -> throw IllegalArgumentException("bitStrength must be $BIT_STRENGTH_128 or $BIT_STRENGTH_256")
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy