/********************************************************************
 * @author:      Kaven
 * @email:       kaven@wuwenkai.com
 * @website:     http://kavenblog.com
 * @file:        [Kaven-Basic] /src/libs/hashing_algorithms/KavenSHA3.ts
 * @create:      2019-04-05 22:50:29.192
 * @modify:      2019-04-25 10:35:20.550
 * @version:     1.1.20
 * @times:       135
 * @lines:       545
 * @copyright:   Copyright © 2019 Kaven. All Rights Reserved.
 * @description: [description]
 * @license:
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 ********************************************************************/
import { KavenUInt64 } from "../KavenUInt64";
import { Strings } from "../Strings";
export class KavenSHA3 {
    /**
     * Keccak-f[b]
     * @param b b∈{25,50,100,200,400,800,1600} is the width of the permutation.
     *
     * The width of the permutation is also the width of the state in the sponge construction.
     * @see https://keccak.team/keccak_specs_summary.html
     */
    KeccakF(a, b = 1600) {
        let n = 24;
        if (b !== 1600) {
            // w∈{1,2,4,8,16,32,64}  and b=25w
            const w = b / 25;
            // 2l=w
            const l = w.GetBaseLog(2);
            // n=12+2l
            n = 12 + 2 * l;
        }
        // for i in 0…n-1
        for (let i = 0; i < n; i++) {
            // A = Round[b](A, RC[i])
            a = this.Round(a, KavenSHA3.RC[i]);
            // this.PrintState(a);
        }
        // return A
        return a;
    }
    /**
     * Keccak[r,c] sponge function, with parameters capacity c and bitrate r.
     * @param message
     * @param r bitrate
     * @param c capacity
     * @param xof
     * @param len message digest output length in bits
     * @param b
     */
    async Keccak(data, r, c, xof = false, len, b = 1600) {
        if (len === undefined) {
            len = c / 2; // message digest output length in bits
        }
        const hexStringLen = len / 4;
        //#region Padding
        /*

            For most applications, the message is byte-aligned, i.e., len(M) = 8m for a nonnegative integer m.
            In this case, the total number of bytes, denoted by q, that are appended to the message is
            determined as follows by m and the rate r:

            q = (r/8) – (m mod (r/8)).

        */
        // [NIST.FIPS.202.pdf]
        // B.2 Hexadecimal Form of Padding Bits
        const paddingBytes = [];
        const m = data.TotalLengthInBytes;
        const q = r / 8 - (m % (r / 8));
        if (xof) {
            if (q === 1) {
                // M || 0x9F
                paddingBytes.push(0x9F);
            }
            else if (q === 2) {
                // M || 0x1F80
                paddingBytes.push(0x1F);
                paddingBytes.push(0x80);
            }
            else if (q > 2) {
                // M || 0x1F || 0x00 … || 0x80
                paddingBytes.push(0x1F);
                for (let i = 0; i < q - 2; i++) {
                    paddingBytes.push(0x00);
                }
                paddingBytes.push(0x80);
            }
        }
        else {
            if (q === 1) {
                // M || 0x86
                paddingBytes.push(0x86);
            }
            else if (q === 2) {
                // M || 0x0680
                paddingBytes.push(0x06);
                paddingBytes.push(0x80);
            }
            else if (q > 2) {
                // M || 0x06 || 0x00 … || 0x80
                paddingBytes.push(0x06);
                for (let i = 0; i < q - 2; i++) {
                    paddingBytes.push(0x00);
                }
                paddingBytes.push(0x80);
            }
        }
        //#endregion
        //#region Initialization
        // for (x,y) in (0…4,0…4)
        // let S: KavenUInt64[][] = "0".repeat(25).ToArray().map(p => new KavenUInt64(p)).Split(5);
        let S = KavenUInt64.Initialize2d(5, 5);
        //#endregion
        //#region Absorbing phase
        const w = b / 25;
        const rDividedByW = r / w;
        const blockSizeInBytes = r / 8; // same to r / w * 8
        // const P = message.Split(blockSizeInBytes, true)
        //             .map(p => p.Split(8, true).map(bits => KavenUInt64.FromBytes(...bits).Swap()));
        const blockCount = (m + paddingBytes.length) / blockSizeInBytes;
        const blocksPerRead = data.BlocksPerRead || 1024;
        const bufferSize = blocksPerRead * blockSizeInBytes;
        let offset = 0;
        let blockIndex = blocksPerRead;
        let buffer = [];
        // for each block Pi in P
        // for (const Pi of P) {
        for (let index = 0; index < blockCount; index++) {
            const isLastBlock = index === blockCount - 1;
            if (blockIndex >= blocksPerRead && offset < m) {
                buffer = await data.AcquiredData(offset, bufferSize);
                offset += buffer.length;
                blockIndex = 0;
            }
            // const PiData = buffer.splice(0, blockSizeInBytes);
            const start = blockIndex * blockSizeInBytes;
            const PiData = buffer.slice(start, start + blockSizeInBytes);
            blockIndex++;
            if (isLastBlock) {
                PiData.push(...paddingBytes);
            }
            else {
                if (PiData.length !== blockSizeInBytes) {
                    throw new Error(`Invalid length: ${PiData.length}, index: ${index}`);
                }
            }
            const Pi = PiData.Split(8, true).map(bits => KavenUInt64.FromBytes(...bits));
            if (rDividedByW === 17) {
                S[0][0] = S[0][0].XOR(Pi[0]);
                S[0][1] = S[0][1].XOR(Pi[5]);
                S[0][2] = S[0][2].XOR(Pi[10]);
                S[0][3] = S[0][3].XOR(Pi[15]);
                S[1][0] = S[1][0].XOR(Pi[1]);
                S[1][1] = S[1][1].XOR(Pi[6]);
                S[1][2] = S[1][2].XOR(Pi[11]);
                S[1][3] = S[1][3].XOR(Pi[16]);
                S[2][0] = S[2][0].XOR(Pi[2]);
                S[2][1] = S[2][1].XOR(Pi[7]);
                S[2][2] = S[2][2].XOR(Pi[12]);
                S[3][0] = S[3][0].XOR(Pi[3]);
                S[3][1] = S[3][1].XOR(Pi[8]);
                S[3][2] = S[3][2].XOR(Pi[13]);
                S[4][0] = S[4][0].XOR(Pi[4]);
                S[4][1] = S[4][1].XOR(Pi[9]);
                S[4][2] = S[4][2].XOR(Pi[14]);
            }
            else {
                // for (x,y) such that x+5*y < r/w
                for (let x = 0; x < 5; x++) {
                    for (let y = 0; y < 5; y++) {
                        if (x + 5 * y < rDividedByW) {
                            // S[x,y] = S[x,y] xor Pi[x+5*y];
                            S[x][y] = S[x][y].XOR(Pi[x + 5 * y]);
                        }
                    }
                }
            }
            // S = Keccak-f[r+c](S);
            S = this.KeccakF(S, b);
        }
        // // for (x,y) such that x+5*y < r/w
        // for (let i = 0; i < message.length; i += blockSizeInBytes) {
        //     for (let j = 0; j < rDividedByW; j++) {
        //         const base = i + j * 8;
        //         // const Pi = KavenUInt64.FromBytes(...message.slice(base + 4, base + 8).reverse()
        //         //     .concat(message.slice(base, base + 4).reverse()));
        //         const Pi = KavenUInt64.FromBytes(...message.slice(base, base + 8)).Swap();
        //         const x = j % 5;
        //         const y = Math.floor(j / 5);
        //         // S[x,y] = S[x,y] xor Pi[x+5*y]
        //         S[x][y] = S[x][y].XOR(Pi);
        //     }
        //     // S = Keccak-f[r+c](S)
        //     S = this.KeccakF(S);
        // }
        //#endregion
        //#region Squeezing phase
        // const planes = S.map((_, i) => S.map(p => p[i]));
        // // get little-endian hex string
        // const Z = planes.map(p => p.map(v => v.Swap().HEX).join(Strings.Empty)).join(Strings.Empty);
        let Z = Strings.Empty;
        while (true) {
            // 3.1.3 Converting State Arrays to Strings
            /*
                For each integer j such that 0≤ j<5, define the string Plane(j) by

                    Plane(j)= Lane(0, j) || Lane(1, j) || Lane(2, j) || Lane(3, j) || Lane(4, j).
                Then

                    S= Plane(0) || Plane(1) || Plane(2) || Plane(3) || Plane(4).
            */
            for (let y = 0; y < 5; y++) {
                for (let x = 0; x < 5; x++) {
                    if ((x + 5 * y) < rDividedByW) {
                        // Z = Z || S[x,y];
                        Z += S[x][y].Swap().HEX;
                    }
                }
            }
            if (Z.length >= hexStringLen) {
                break;
            }
            S = this.KeccakF(S, b);
        }
        //#endregion
        return Z.slice(0, hexStringLen);
    }
    Compute224(message) {
        return this.Keccak(this.ConvertMessage(message), 1152, 448);
    }
    Compute256(message) {
        return this.Keccak(this.ConvertMessage(message), 1088, 512);
    }
    Compute384(message) {
        return this.Keccak(this.ConvertMessage(message), 832, 768);
    }
    Compute512(message) {
        return this.Keccak(this.ConvertMessage(message), 576, 1024);
    }
    ComputeSHAKE128(message, len) {
        return this.Keccak(this.ConvertMessage(message), 1344, 256, true, len);
    }
    ComputeSHAKE256(message, len) {
        return this.Keccak(this.ConvertMessage(message), 1088, 512, true, len);
    }
    PrintState(state) {
        const str = state.map(x => x.map(y => y.HEX).join(", ")).join(Strings.CR_LF);
        console.log(Strings.CR_LF + str);
    }
    ConvertMessage(message) {
        let data;
        if (Array.isArray(message)) {
            data = {
                AcquiredData: async (offset, length) => message.slice(offset, offset + length),
                TotalLengthInBytes: message.length,
            };
        }
        else {
            data = message;
        }
        return data;
    }
    Round(a, rc) {
        // const B: KavenUInt64[][] = "0".repeat(25).ToArray().map(p => new KavenUInt64(p)).Split(5);
        const B = KavenUInt64.Initialize2d(5, 5);
        const C = new Array(5);
        const D = new Array(5);
        /// # θ step, [Keccak §2.3.2]
        // for x = 0 to 4 do
        // for (let x = 0; x < 5; x++) {
        //     // C[x] = A[x,0] xor A[x,1] xor A[x,2] xor A[x,3] xor A[x,4]
        //     C[x] = a[x][0].XOR(a[x][1]).XOR(a[x][2]).XOR(a[x][3]).XOR(a[x][4]);
        // }
        C[0] = a[0][0].XOR(a[0][1]).XOR(a[0][2]).XOR(a[0][3]).XOR(a[0][4]);
        C[1] = a[1][0].XOR(a[1][1]).XOR(a[1][2]).XOR(a[1][3]).XOR(a[1][4]);
        C[2] = a[2][0].XOR(a[2][1]).XOR(a[2][2]).XOR(a[2][3]).XOR(a[2][4]);
        C[3] = a[3][0].XOR(a[3][1]).XOR(a[3][2]).XOR(a[3][3]).XOR(a[3][4]);
        C[4] = a[4][0].XOR(a[4][1]).XOR(a[4][2]).XOR(a[4][3]).XOR(a[4][4]);
        // for x = 0 to 4 do
        // for (let x = 0; x < 5; x++) {
        //     // D[x] = C[x-1] xor rot(C[x+1],1)
        //     D[x] = C[(x - 1).Mod(5)].XOR(C[(x + 1).Mod(5)].LeftRotate(1));
        // }
        D[0] = C[4].XOR(C[1].LeftRotate(1));
        D[1] = C[0].XOR(C[2].LeftRotate(1));
        D[2] = C[1].XOR(C[3].LeftRotate(1));
        D[3] = C[2].XOR(C[4].LeftRotate(1));
        D[4] = C[3].XOR(C[0].LeftRotate(1));
        // for (x,y) in (0…4,0…4)
        // for (let x = 0; x < 5; x++) {
        //     for (let y = 0; y < 5; y++) {
        //         // A[x,y] = A[x,y] xor D[x]
        //         a[x][y] = a[x][y].XOR(D[x]);
        //     }
        // }
        a[0][0] = a[0][0].XOR(D[0]);
        a[0][1] = a[0][1].XOR(D[0]);
        a[0][2] = a[0][2].XOR(D[0]);
        a[0][3] = a[0][3].XOR(D[0]);
        a[0][4] = a[0][4].XOR(D[0]);
        a[1][0] = a[1][0].XOR(D[1]);
        a[1][1] = a[1][1].XOR(D[1]);
        a[1][2] = a[1][2].XOR(D[1]);
        a[1][3] = a[1][3].XOR(D[1]);
        a[1][4] = a[1][4].XOR(D[1]);
        a[2][0] = a[2][0].XOR(D[2]);
        a[2][1] = a[2][1].XOR(D[2]);
        a[2][2] = a[2][2].XOR(D[2]);
        a[2][3] = a[2][3].XOR(D[2]);
        a[2][4] = a[2][4].XOR(D[2]);
        a[3][0] = a[3][0].XOR(D[3]);
        a[3][1] = a[3][1].XOR(D[3]);
        a[3][2] = a[3][2].XOR(D[3]);
        a[3][3] = a[3][3].XOR(D[3]);
        a[3][4] = a[3][4].XOR(D[3]);
        a[4][0] = a[4][0].XOR(D[4]);
        a[4][1] = a[4][1].XOR(D[4]);
        a[4][2] = a[4][2].XOR(D[4]);
        a[4][3] = a[4][3].XOR(D[4]);
        a[4][4] = a[4][4].XOR(D[4]);
        /// # ρ and π steps, [Keccak §2.3.4]
        // for (x,y) in (0…4,0…4)
        // for (let x = 0; x < 5; x++) {
        //     for (let y = 0; y < 5; y++) {
        //         // B[y,2*x+3*y] = rot(A[x,y], r[x,y])
        //         B[y][(2 * x + 3 * y).Mod(5)] = a[x][y].LeftRotate(KavenSHA3.r[x][y]);
        //     }
        // }
        B[0][0] = a[0][0].LeftRotate(KavenSHA3.r[0][0]);
        B[1][3] = a[0][1].LeftRotate(KavenSHA3.r[0][1]);
        B[2][1] = a[0][2].LeftRotate(KavenSHA3.r[0][2]);
        B[3][4] = a[0][3].LeftRotate(KavenSHA3.r[0][3]);
        B[4][2] = a[0][4].LeftRotate(KavenSHA3.r[0][4]);
        B[0][2] = a[1][0].LeftRotate(KavenSHA3.r[1][0]);
        B[1][0] = a[1][1].LeftRotate(KavenSHA3.r[1][1]);
        B[2][3] = a[1][2].LeftRotate(KavenSHA3.r[1][2]);
        B[3][1] = a[1][3].LeftRotate(KavenSHA3.r[1][3]);
        B[4][4] = a[1][4].LeftRotate(KavenSHA3.r[1][4]);
        B[0][4] = a[2][0].LeftRotate(KavenSHA3.r[2][0]);
        B[1][2] = a[2][1].LeftRotate(KavenSHA3.r[2][1]);
        B[2][0] = a[2][2].LeftRotate(KavenSHA3.r[2][2]);
        B[3][3] = a[2][3].LeftRotate(KavenSHA3.r[2][3]);
        B[4][1] = a[2][4].LeftRotate(KavenSHA3.r[2][4]);
        B[0][1] = a[3][0].LeftRotate(KavenSHA3.r[3][0]);
        B[1][4] = a[3][1].LeftRotate(KavenSHA3.r[3][1]);
        B[2][2] = a[3][2].LeftRotate(KavenSHA3.r[3][2]);
        B[3][0] = a[3][3].LeftRotate(KavenSHA3.r[3][3]);
        B[4][3] = a[3][4].LeftRotate(KavenSHA3.r[3][4]);
        B[0][3] = a[4][0].LeftRotate(KavenSHA3.r[4][0]);
        B[1][1] = a[4][1].LeftRotate(KavenSHA3.r[4][1]);
        B[2][4] = a[4][2].LeftRotate(KavenSHA3.r[4][2]);
        B[3][2] = a[4][3].LeftRotate(KavenSHA3.r[4][3]);
        B[4][0] = a[4][4].LeftRotate(KavenSHA3.r[4][4]);
        /// # χ step
        // for (x,y) in (0…4,0…4)
        // for (let x = 0; x < 5; x++) {
        //     for (let y = 0; y < 5; y++) {
        //         // A[x,y] = B[x,y] xor ((not B[x+1,y]) and B[x+2,y])
        //         a[x][y] = B[x][y].XOR((B[(x + 1).Mod(5)][y].NOT()).AND(B[(x + 2).Mod(5)][y]));
        //         console.log(x, y, (x + 1).Mod(5), (x + 2).Mod(5));
        //     }
        // }
        a[0][0] = B[0][0].XOR((B[1][0].NOT()).AND(B[2][0]));
        a[0][1] = B[0][1].XOR((B[1][1].NOT()).AND(B[2][1]));
        a[0][2] = B[0][2].XOR((B[1][2].NOT()).AND(B[2][2]));
        a[0][3] = B[0][3].XOR((B[1][3].NOT()).AND(B[2][3]));
        a[0][4] = B[0][4].XOR((B[1][4].NOT()).AND(B[2][4]));
        a[1][0] = B[1][0].XOR((B[2][0].NOT()).AND(B[3][0]));
        a[1][1] = B[1][1].XOR((B[2][1].NOT()).AND(B[3][1]));
        a[1][2] = B[1][2].XOR((B[2][2].NOT()).AND(B[3][2]));
        a[1][3] = B[1][3].XOR((B[2][3].NOT()).AND(B[3][3]));
        a[1][4] = B[1][4].XOR((B[2][4].NOT()).AND(B[3][4]));
        a[2][0] = B[2][0].XOR((B[3][0].NOT()).AND(B[4][0]));
        a[2][1] = B[2][1].XOR((B[3][1].NOT()).AND(B[4][1]));
        a[2][2] = B[2][2].XOR((B[3][2].NOT()).AND(B[4][2]));
        a[2][3] = B[2][3].XOR((B[3][3].NOT()).AND(B[4][3]));
        a[2][4] = B[2][4].XOR((B[3][4].NOT()).AND(B[4][4]));
        a[3][0] = B[3][0].XOR((B[4][0].NOT()).AND(B[0][0]));
        a[3][1] = B[3][1].XOR((B[4][1].NOT()).AND(B[0][1]));
        a[3][2] = B[3][2].XOR((B[4][2].NOT()).AND(B[0][2]));
        a[3][3] = B[3][3].XOR((B[4][3].NOT()).AND(B[0][3]));
        a[3][4] = B[3][4].XOR((B[4][4].NOT()).AND(B[0][4]));
        a[4][0] = B[4][0].XOR((B[0][0].NOT()).AND(B[1][0]));
        a[4][1] = B[4][1].XOR((B[0][1].NOT()).AND(B[1][1]));
        a[4][2] = B[4][2].XOR((B[0][2].NOT()).AND(B[1][2]));
        a[4][3] = B[4][3].XOR((B[0][3].NOT()).AND(B[1][3]));
        a[4][4] = B[4][4].XOR((B[0][4].NOT()).AND(B[1][4]));
        ///  # ι step
        // A[0,0] = A[0,0] xor RC
        a[0][0] = a[0][0].XOR(rc);
        return a;
    }
}
/**
 * Round constants
 */
KavenSHA3.RC = [
    "0000000000000001", "0000000000008082", "800000000000808a",
    "8000000080008000", "000000000000808b", "0000000080000001",
    "8000000080008081", "8000000000008009", "000000000000008a",
    "0000000000000088", "0000000080008009", "000000008000000a",
    "000000008000808b", "800000000000008b", "8000000000008089",
    "8000000000008003", "8000000000008002", "8000000000000080",
    "000000000000800a", "800000008000000a", "8000000080008081",
    "8000000000008080", "0000000080000001", "8000000080008008",
].map(p => new KavenUInt64(p, 16));
/**
 * Rotation offsets
 */
KavenSHA3.r = [
    [0, 36, 3, 41, 18],
    [1, 44, 10, 45, 2],
    [62, 6, 43, 15, 61],
    [28, 55, 25, 21, 56],
    [27, 20, 39, 8, 14],
];
//# sourceMappingURL=KavenSHA3.js.map