/**
 * Computes SHA hashes of arbitrary data. SHA hashes are like a
 * checksum or CRC, but are more robust.
 *
 * This is a copy of std.digest.sha, but stripped of bells and whistles
 *
 * Compiler implementation of the
 * $(LINK2 https://www.dlang.org, D programming language).
 *
 * Copyright:    Copyright (C) 2012-2026 by The D Language Foundation, All Rights Reserved
 * Authors:     $(LINK2 https://www.digitalmars.com, Walter Bright)
 * License:     $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
 * Source:      $(LINK2 https://github.com/dlang/dmd/blob/master/compiler/src/dmd/common/md5.d)
 * Documentation:  https://dlang.org/phobos/dmd_common_md5.html
 * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/compiler/src/dmd/common/md5.d
 */

module dmd.common.sha;

import core.bitop;

/*
* Helper methods for encoding the buffer.
* Can be removed if the optimizer can inline the methods from std.bitmanip.
*/
version (LittleEndian)
{
    private alias nativeToBigEndian = bswap;
    private alias bigEndianToNative = bswap;
}
else pragma(inline, true) private pure @nogc nothrow @safe
{
    uint nativeToBigEndian(uint val) { return val; }
    ulong nativeToBigEndian(ulong val) { return val; }
    alias bigEndianToNative = nativeToBigEndian;
}

/**
* Template API SHA1/SHA2 implementation. Supports: SHA-1, SHA-224, SHA-256,
* SHA-384, SHA-512, SHA-512/224 and SHA-512/256.
*
* The hashBlockSize and digestSize are in bits. However, it's likely easier to
* simply use the convenience aliases: SHA1, SHA224, SHA256, SHA384, SHA512,
* SHA512_224 and SHA512_256.
*
* See `std.digest` for differences between template and OOP API.
*/
struct SHA(uint hashBlockSize, uint digestSize)
{
    enum blockSize = hashBlockSize;

    static assert(blockSize == 512 || blockSize == 1024,
                  "Invalid SHA blockSize, must be 512 or 1024");
    static assert(digestSize == 160 || digestSize == 224 || digestSize == 256 || digestSize == 384 || digestSize == 512,
                  "Invalid SHA digestSize, must be 224, 256, 384 or 512");
    static assert(!(blockSize == 512 && digestSize > 256),
                  "Invalid SHA digestSize for a blockSize of 512. The digestSize must be 160, 224 or 256.");
    static assert(!(blockSize == 1024 && digestSize < 224),
                  "Invalid SHA digestSize for a blockSize of 1024. The digestSize must be 224, 256, 384 or 512.");

    static if (digestSize == 160) /* SHA-1 */
    {
        version (USE_SSSE3)
        {
            import core.cpuid : ssse3;
            import std.internal.digest.sha_SSSE3 : sse3_constants=constants, transformSSSE3;

            static void transform(uint[5]* state, const(ubyte[64])* block) pure nothrow @nogc
            {
                if (ssse3)
                {
                    version (D_InlineAsm_X86_64)
                        // constants as extra argument for PIC
                        // see https://issues.dlang.org/show_bug.cgi?id=9378
                        transformSSSE3(state, block, &sse3_constants);
                    else
                        transformSSSE3(state, block);
                }
                else
                    transformX86(state, block);
            }
        }
        else
        {
            alias transform = transformX86;
        }
    }
    else static if (blockSize == 512) /* SHA-224, SHA-256 */
        alias transform = transformSHA2!uint;
    else static if (blockSize == 1024) /* SHA-384, SHA-512, SHA-512/224, SHA-512/256 */
        alias transform = transformSHA2!ulong;
    else
        static assert(0);

private:
    /* magic initialization constants - state (ABCDEFGH) */
    static if (blockSize == 512 && digestSize == 160) /* SHA-1 */
    {
        uint[5] state =
        [0x67452301,0xefcdab89,0x98badcfe,0x10325476,0xc3d2e1f0];
    }
    else static if (blockSize == 512 && digestSize == 224) /* SHA-224 */
    {
        uint[8] state = [
            0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939,
            0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4,
        ];
    }
    else static if (blockSize == 512 && digestSize == 256) /* SHA-256 */
    {
        uint[8] state = [
            0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
            0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
        ];
    }
    else static if (blockSize == 1024 && digestSize == 224) /* SHA-512/224 */
    {
        ulong[8] state = [
            0x8C3D37C8_19544DA2, 0x73E19966_89DCD4D6,
            0x1DFAB7AE_32FF9C82, 0x679DD514_582F9FCF,
            0x0F6D2B69_7BD44DA8, 0x77E36F73_04C48942,
            0x3F9D85A8_6A1D36C8, 0x1112E6AD_91D692A1,
        ];
    }
    else static if (blockSize == 1024 && digestSize == 256) /* SHA-512/256 */
    {
        ulong[8] state = [
            0x22312194_FC2BF72C, 0x9F555FA3_C84C64C2,
            0x2393B86B_6F53B151, 0x96387719_5940EABD,
            0x96283EE2_A88EFFE3, 0xBE5E1E25_53863992,
            0x2B0199FC_2C85B8AA, 0x0EB72DDC_81C52CA2,
        ];
    }
    else static if (blockSize == 1024 && digestSize == 384) /* SHA-384 */
    {
        ulong[8] state = [
            0xcbbb9d5d_c1059ed8, 0x629a292a_367cd507,
            0x9159015a_3070dd17, 0x152fecd8_f70e5939,
            0x67332667_ffc00b31, 0x8eb44a87_68581511,
            0xdb0c2e0d_64f98fa7, 0x47b5481d_befa4fa4,
        ];
    }
    else static if (blockSize == 1024 && digestSize == 512) /* SHA-512 */
    {
        ulong[8] state = [
            0x6a09e667_f3bcc908, 0xbb67ae85_84caa73b,
            0x3c6ef372_fe94f82b, 0xa54ff53a_5f1d36f1,
            0x510e527f_ade682d1, 0x9b05688c_2b3e6c1f,
            0x1f83d9ab_fb41bd6b, 0x5be0cd19_137e2179,
        ];
    }
    else
        static assert(0);

    /* constants */
    static if (blockSize == 512)
    {
        static immutable uint[64] constants = [
            0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
            0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
            0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
            0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
            0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
            0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
            0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
            0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
        ];
    }
    else static if (blockSize == 1024)
    {
        static immutable ulong[80] constants = [
            0x428a2f98_d728ae22, 0x71374491_23ef65cd, 0xb5c0fbcf_ec4d3b2f, 0xe9b5dba5_8189dbbc,
            0x3956c25b_f348b538, 0x59f111f1_b605d019, 0x923f82a4_af194f9b, 0xab1c5ed5_da6d8118,
            0xd807aa98_a3030242, 0x12835b01_45706fbe, 0x243185be_4ee4b28c, 0x550c7dc3_d5ffb4e2,
            0x72be5d74_f27b896f, 0x80deb1fe_3b1696b1, 0x9bdc06a7_25c71235, 0xc19bf174_cf692694,
            0xe49b69c1_9ef14ad2, 0xefbe4786_384f25e3, 0x0fc19dc6_8b8cd5b5, 0x240ca1cc_77ac9c65,
            0x2de92c6f_592b0275, 0x4a7484aa_6ea6e483, 0x5cb0a9dc_bd41fbd4, 0x76f988da_831153b5,
            0x983e5152_ee66dfab, 0xa831c66d_2db43210, 0xb00327c8_98fb213f, 0xbf597fc7_beef0ee4,
            0xc6e00bf3_3da88fc2, 0xd5a79147_930aa725, 0x06ca6351_e003826f, 0x14292967_0a0e6e70,
            0x27b70a85_46d22ffc, 0x2e1b2138_5c26c926, 0x4d2c6dfc_5ac42aed, 0x53380d13_9d95b3df,
            0x650a7354_8baf63de, 0x766a0abb_3c77b2a8, 0x81c2c92e_47edaee6, 0x92722c85_1482353b,
            0xa2bfe8a1_4cf10364, 0xa81a664b_bc423001, 0xc24b8b70_d0f89791, 0xc76c51a3_0654be30,
            0xd192e819_d6ef5218, 0xd6990624_5565a910, 0xf40e3585_5771202a, 0x106aa070_32bbd1b8,
            0x19a4c116_b8d2d0c8, 0x1e376c08_5141ab53, 0x2748774c_df8eeb99, 0x34b0bcb5_e19b48a8,
            0x391c0cb3_c5c95a63, 0x4ed8aa4a_e3418acb, 0x5b9cca4f_7763e373, 0x682e6ff3_d6b2b8a3,
            0x748f82ee_5defb2fc, 0x78a5636f_43172f60, 0x84c87814_a1f0ab72, 0x8cc70208_1a6439ec,
            0x90befffa_23631e28, 0xa4506ceb_de82bde9, 0xbef9a3f7_b2c67915, 0xc67178f2_e372532b,
            0xca273ece_ea26619c, 0xd186b8c7_21c0c207, 0xeada7dd6_cde0eb1e, 0xf57d4f7f_ee6ed178,
            0x06f067aa_72176fba, 0x0a637dc5_a2c898a6, 0x113f9804_bef90dae, 0x1b710b35_131c471b,
            0x28db77f5_23047d84, 0x32caab7b_40c72493, 0x3c9ebe0a_15c9bebc, 0x431d67c4_9c100d4c,
            0x4cc5d4be_cb3e42b6, 0x597f299c_fc657e2a, 0x5fcb6fab_3ad6faec, 0x6c44198c_4a475817,
        ];
    }
    else
        static assert(0);

    /*
    * number of bits, modulo 2^64 (ulong[1]) or 2^128 (ulong[2]),
    * should just use ucent instead of ulong[2] once it's available
    */
    ulong[blockSize/512] count;
    ubyte[blockSize/8]   buffer; /* input buffer */

    static immutable ubyte[128] padding =
    [
        0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

        0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
    ];

    /*
    * Basic SHA1/SHA2 functions.
    */
    // pragma(inline, true) not supported by all bootstrap compilers
        static @safe pure nothrow @nogc
        {
            /* All SHA1/SHA2 */
            T Ch(T)(T x, T y, T z) { return z ^ (x & (y ^ z)); }
            T Maj(T)(T x, T y, T z) { return (x & y) | (z & (x ^ y)); }

            /* SHA-1 */
            uint Parity(uint x, uint y, uint z) { return x ^ y ^ z; }

            /* SHA-224, SHA-256 */
            uint BigSigma0(uint x) { return core.bitop.ror(x, 2) ^ core.bitop.ror(x, 13) ^ core.bitop.ror(x, 22); }
            uint BigSigma1(uint x) { return core.bitop.ror(x, 6) ^ core.bitop.ror(x, 11) ^ core.bitop.ror(x, 25); }
            uint SmSigma0(uint x) { return core.bitop.ror(x, 7) ^ core.bitop.ror(x, 18) ^ x >> 3; }
            uint SmSigma1(uint x) { return core.bitop.ror(x, 17) ^ core.bitop.ror(x, 19) ^ x >> 10; }

            /* SHA-384, SHA-512, SHA-512/224, SHA-512/256 */
            ulong BigSigma0(ulong x) { return core.bitop.ror(x, 28) ^ core.bitop.ror(x, 34) ^ core.bitop.ror(x, 39); }
            ulong BigSigma1(ulong x) { return core.bitop.ror(x, 14) ^ core.bitop.ror(x, 18) ^ core.bitop.ror(x, 41); }
            ulong SmSigma0(ulong x) { return core.bitop.ror(x, 1) ^ core.bitop.ror(x, 8) ^ x >> 7; }
            ulong SmSigma1(ulong x) { return core.bitop.ror(x, 19) ^ core.bitop.ror(x, 61) ^ x >> 6; }
        }

    /*
    * SHA1 basic transformation. Transforms state based on block.
    */
    static void T_0_15(int i, const(ubyte[64])* input, ref uint[16] W, uint A, ref uint B, uint C, uint D,
                       uint E, ref uint T) pure nothrow @nogc
                       {
                        uint Wi = W[i] = bigEndianToNative(*cast(uint*) &((*input)[i*4]));
                        T = Ch(B, C, D) + E + core.bitop.rol(A, 5) + Wi + 0x5a827999;
                        B = core.bitop.rol(B, 30);
                       }

    static void T_16_19(int i, ref uint[16] W, uint A, ref uint B, uint C, uint D, uint E, ref uint T)
        pure nothrow @nogc
        {
            W[i&15] = core.bitop.rol(W[(i-3)&15] ^ W[(i-8)&15] ^ W[(i-14)&15] ^ W[(i-16)&15], 1);
            T = Ch(B, C, D) + E + core.bitop.rol(A, 5) + W[i&15] + 0x5a827999;
            B = core.bitop.rol(B, 30);
        }

    static void T_20_39(int i, ref uint[16] W, uint A, ref uint B, uint C, uint D, uint E,
                        ref uint T) pure nothrow @nogc
                        {
                            W[i&15] = core.bitop.rol(W[(i-3)&15] ^ W[(i-8)&15] ^ W[(i-14)&15] ^ W[(i-16)&15], 1);
                            T = Parity(B, C, D) + E + core.bitop.rol(A, 5) + W[i&15] + 0x6ed9eba1;
                            B = core.bitop.rol(B, 30);
                        }

    static void T_40_59(int i, ref uint[16] W, uint A, ref uint B, uint C, uint D, uint E,
                        ref uint T) pure nothrow @nogc
                        {
                            W[i&15] = core.bitop.rol(W[(i-3)&15] ^ W[(i-8)&15] ^ W[(i-14)&15] ^ W[(i-16)&15], 1);
                            T = Maj(B, C, D) + E + core.bitop.rol(A, 5) + W[i&15] + 0x8f1bbcdc;
                            B = core.bitop.rol(B, 30);
                        }

    static void T_60_79(int i, ref uint[16] W, uint A, ref uint B, uint C, uint D, uint E,
                        ref uint T) pure nothrow @nogc
                        {
                            W[i&15] = core.bitop.rol(W[(i-3)&15] ^ W[(i-8)&15] ^ W[(i-14)&15] ^ W[(i-16)&15], 1);
                            T = Parity(B, C, D) + E + core.bitop.rol(A, 5) + W[i&15] + 0xca62c1d6;
                            B = core.bitop.rol(B, 30);
                        }

    private static void transformX86(uint[5]* state, const(ubyte[64])* block) pure nothrow @nogc
    {
        uint A, B, C, D, E, T;
        uint[16] W = void;

        A = (*state)[0];
        B = (*state)[1];
        C = (*state)[2];
        D = (*state)[3];
        E = (*state)[4];

        T_0_15 ( 0, block, W, A, B, C, D, E, T);
        T_0_15 ( 1, block, W, T, A, B, C, D, E);
        T_0_15 ( 2, block, W, E, T, A, B, C, D);
        T_0_15 ( 3, block, W, D, E, T, A, B, C);
        T_0_15 ( 4, block, W, C, D, E, T, A, B);
        T_0_15 ( 5, block, W, B, C, D, E, T, A);
        T_0_15 ( 6, block, W, A, B, C, D, E, T);
        T_0_15 ( 7, block, W, T, A, B, C, D, E);
        T_0_15 ( 8, block, W, E, T, A, B, C, D);
        T_0_15 ( 9, block, W, D, E, T, A, B, C);
        T_0_15 (10, block, W, C, D, E, T, A, B);
        T_0_15 (11, block, W, B, C, D, E, T, A);
        T_0_15 (12, block, W, A, B, C, D, E, T);
        T_0_15 (13, block, W, T, A, B, C, D, E);
        T_0_15 (14, block, W, E, T, A, B, C, D);
        T_0_15 (15, block, W, D, E, T, A, B, C);
        T_16_19(16, W, C, D, E, T, A, B);
        T_16_19(17, W, B, C, D, E, T, A);
        T_16_19(18, W, A, B, C, D, E, T);
        T_16_19(19, W, T, A, B, C, D, E);
        T_20_39(20, W, E, T, A, B, C, D);
        T_20_39(21, W, D, E, T, A, B, C);
        T_20_39(22, W, C, D, E, T, A, B);
        T_20_39(23, W, B, C, D, E, T, A);
        T_20_39(24, W, A, B, C, D, E, T);
        T_20_39(25, W, T, A, B, C, D, E);
        T_20_39(26, W, E, T, A, B, C, D);
        T_20_39(27, W, D, E, T, A, B, C);
        T_20_39(28, W, C, D, E, T, A, B);
        T_20_39(29, W, B, C, D, E, T, A);
        T_20_39(30, W, A, B, C, D, E, T);
        T_20_39(31, W, T, A, B, C, D, E);
        T_20_39(32, W, E, T, A, B, C, D);
        T_20_39(33, W, D, E, T, A, B, C);
        T_20_39(34, W, C, D, E, T, A, B);
        T_20_39(35, W, B, C, D, E, T, A);
        T_20_39(36, W, A, B, C, D, E, T);
        T_20_39(37, W, T, A, B, C, D, E);
        T_20_39(38, W, E, T, A, B, C, D);
        T_20_39(39, W, D, E, T, A, B, C);
        T_40_59(40, W, C, D, E, T, A, B);
        T_40_59(41, W, B, C, D, E, T, A);
        T_40_59(42, W, A, B, C, D, E, T);
        T_40_59(43, W, T, A, B, C, D, E);
        T_40_59(44, W, E, T, A, B, C, D);
        T_40_59(45, W, D, E, T, A, B, C);
        T_40_59(46, W, C, D, E, T, A, B);
        T_40_59(47, W, B, C, D, E, T, A);
        T_40_59(48, W, A, B, C, D, E, T);
        T_40_59(49, W, T, A, B, C, D, E);
        T_40_59(50, W, E, T, A, B, C, D);
        T_40_59(51, W, D, E, T, A, B, C);
        T_40_59(52, W, C, D, E, T, A, B);
        T_40_59(53, W, B, C, D, E, T, A);
        T_40_59(54, W, A, B, C, D, E, T);
        T_40_59(55, W, T, A, B, C, D, E);
        T_40_59(56, W, E, T, A, B, C, D);
        T_40_59(57, W, D, E, T, A, B, C);
        T_40_59(58, W, C, D, E, T, A, B);
        T_40_59(59, W, B, C, D, E, T, A);
        T_60_79(60, W, A, B, C, D, E, T);
        T_60_79(61, W, T, A, B, C, D, E);
        T_60_79(62, W, E, T, A, B, C, D);
        T_60_79(63, W, D, E, T, A, B, C);
        T_60_79(64, W, C, D, E, T, A, B);
        T_60_79(65, W, B, C, D, E, T, A);
        T_60_79(66, W, A, B, C, D, E, T);
        T_60_79(67, W, T, A, B, C, D, E);
        T_60_79(68, W, E, T, A, B, C, D);
        T_60_79(69, W, D, E, T, A, B, C);
        T_60_79(70, W, C, D, E, T, A, B);
        T_60_79(71, W, B, C, D, E, T, A);
        T_60_79(72, W, A, B, C, D, E, T);
        T_60_79(73, W, T, A, B, C, D, E);
        T_60_79(74, W, E, T, A, B, C, D);
        T_60_79(75, W, D, E, T, A, B, C);
        T_60_79(76, W, C, D, E, T, A, B);
        T_60_79(77, W, B, C, D, E, T, A);
        T_60_79(78, W, A, B, C, D, E, T);
        T_60_79(79, W, T, A, B, C, D, E);

        (*state)[0] += E;
        (*state)[1] += T;
        (*state)[2] += A;
        (*state)[3] += B;
        (*state)[4] += C;

        /* Zeroize sensitive information. */
        W[] = 0;
    }

    /*
    * SHA2 basic transformation. Transforms state based on block.
    */
    // pragma(inline, true) not supported by all bootstrap compilers
        static void T_SHA2_0_15(Word)(int i, const(ubyte[blockSize/8])* input, ref Word[16] W,
                                      Word A, Word B, Word C, ref Word D, Word E, Word F, Word G, ref Word H, Word K)
            pure nothrow @nogc
            {
                Word Wi = W[i] = bigEndianToNative(*cast(Word*) &((*input)[i*Word.sizeof]));
                Word T1 = H + BigSigma1(E) + Ch(E, F, G) + K + Wi;
                Word T2 = BigSigma0(A) + Maj(A, B, C);
                D += T1;
                H = T1 + T2;
            }

    // Temporarily disable inlining because it increases build speed by 10x.
    // pragma(inline, true)
    static void T_SHA2_16_79(Word)(int i, ref Word[16] W,
                                   Word A, Word B, Word C, ref Word D, Word E, Word F, Word G, ref Word H, Word K)
        pure nothrow @nogc
        {
            W[i&15] = SmSigma1(W[(i-2)&15]) + W[(i-7)&15] + SmSigma0(W[(i-15)&15]) + W[i&15];
            Word T1 = H + BigSigma1(E) + Ch(E, F, G) + K + W[i&15];
            Word T2 = BigSigma0(A) + Maj(A, B, C);
            D += T1;
            H = T1 + T2;
        }

    private static void transformSHA2(Word)(Word[8]* state, const(ubyte[blockSize/8])* block)
        pure nothrow @nogc
        {
            Word A, B, C, D, E, F, G, H;
            Word[16] W = void;

            A = (*state)[0];
            B = (*state)[1];
            C = (*state)[2];
            D = (*state)[3];
            E = (*state)[4];
            F = (*state)[5];
            G = (*state)[6];
            H = (*state)[7];

            T_SHA2_0_15!Word ( 0, block, W, A, B, C, D, E, F, G, H, constants[ 0]);
            T_SHA2_0_15!Word ( 1, block, W, H, A, B, C, D, E, F, G, constants[ 1]);
            T_SHA2_0_15!Word ( 2, block, W, G, H, A, B, C, D, E, F, constants[ 2]);
            T_SHA2_0_15!Word ( 3, block, W, F, G, H, A, B, C, D, E, constants[ 3]);
            T_SHA2_0_15!Word ( 4, block, W, E, F, G, H, A, B, C, D, constants[ 4]);
            T_SHA2_0_15!Word ( 5, block, W, D, E, F, G, H, A, B, C, constants[ 5]);
            T_SHA2_0_15!Word ( 6, block, W, C, D, E, F, G, H, A, B, constants[ 6]);
            T_SHA2_0_15!Word ( 7, block, W, B, C, D, E, F, G, H, A, constants[ 7]);
            T_SHA2_0_15!Word ( 8, block, W, A, B, C, D, E, F, G, H, constants[ 8]);
            T_SHA2_0_15!Word ( 9, block, W, H, A, B, C, D, E, F, G, constants[ 9]);
            T_SHA2_0_15!Word (10, block, W, G, H, A, B, C, D, E, F, constants[10]);
            T_SHA2_0_15!Word (11, block, W, F, G, H, A, B, C, D, E, constants[11]);
            T_SHA2_0_15!Word (12, block, W, E, F, G, H, A, B, C, D, constants[12]);
            T_SHA2_0_15!Word (13, block, W, D, E, F, G, H, A, B, C, constants[13]);
            T_SHA2_0_15!Word (14, block, W, C, D, E, F, G, H, A, B, constants[14]);
            T_SHA2_0_15!Word (15, block, W, B, C, D, E, F, G, H, A, constants[15]);
            T_SHA2_16_79!Word(16, W, A, B, C, D, E, F, G, H, constants[16]);
            T_SHA2_16_79!Word(17, W, H, A, B, C, D, E, F, G, constants[17]);
            T_SHA2_16_79!Word(18, W, G, H, A, B, C, D, E, F, constants[18]);
            T_SHA2_16_79!Word(19, W, F, G, H, A, B, C, D, E, constants[19]);
            T_SHA2_16_79!Word(20, W, E, F, G, H, A, B, C, D, constants[20]);
            T_SHA2_16_79!Word(21, W, D, E, F, G, H, A, B, C, constants[21]);
            T_SHA2_16_79!Word(22, W, C, D, E, F, G, H, A, B, constants[22]);
            T_SHA2_16_79!Word(23, W, B, C, D, E, F, G, H, A, constants[23]);
            T_SHA2_16_79!Word(24, W, A, B, C, D, E, F, G, H, constants[24]);
            T_SHA2_16_79!Word(25, W, H, A, B, C, D, E, F, G, constants[25]);
            T_SHA2_16_79!Word(26, W, G, H, A, B, C, D, E, F, constants[26]);
            T_SHA2_16_79!Word(27, W, F, G, H, A, B, C, D, E, constants[27]);
            T_SHA2_16_79!Word(28, W, E, F, G, H, A, B, C, D, constants[28]);
            T_SHA2_16_79!Word(29, W, D, E, F, G, H, A, B, C, constants[29]);
            T_SHA2_16_79!Word(30, W, C, D, E, F, G, H, A, B, constants[30]);
            T_SHA2_16_79!Word(31, W, B, C, D, E, F, G, H, A, constants[31]);
            T_SHA2_16_79!Word(32, W, A, B, C, D, E, F, G, H, constants[32]);
            T_SHA2_16_79!Word(33, W, H, A, B, C, D, E, F, G, constants[33]);
            T_SHA2_16_79!Word(34, W, G, H, A, B, C, D, E, F, constants[34]);
            T_SHA2_16_79!Word(35, W, F, G, H, A, B, C, D, E, constants[35]);
            T_SHA2_16_79!Word(36, W, E, F, G, H, A, B, C, D, constants[36]);
            T_SHA2_16_79!Word(37, W, D, E, F, G, H, A, B, C, constants[37]);
            T_SHA2_16_79!Word(38, W, C, D, E, F, G, H, A, B, constants[38]);
            T_SHA2_16_79!Word(39, W, B, C, D, E, F, G, H, A, constants[39]);
            T_SHA2_16_79!Word(40, W, A, B, C, D, E, F, G, H, constants[40]);
            T_SHA2_16_79!Word(41, W, H, A, B, C, D, E, F, G, constants[41]);
            T_SHA2_16_79!Word(42, W, G, H, A, B, C, D, E, F, constants[42]);
            T_SHA2_16_79!Word(43, W, F, G, H, A, B, C, D, E, constants[43]);
            T_SHA2_16_79!Word(44, W, E, F, G, H, A, B, C, D, constants[44]);
            T_SHA2_16_79!Word(45, W, D, E, F, G, H, A, B, C, constants[45]);
            T_SHA2_16_79!Word(46, W, C, D, E, F, G, H, A, B, constants[46]);
            T_SHA2_16_79!Word(47, W, B, C, D, E, F, G, H, A, constants[47]);
            T_SHA2_16_79!Word(48, W, A, B, C, D, E, F, G, H, constants[48]);
            T_SHA2_16_79!Word(49, W, H, A, B, C, D, E, F, G, constants[49]);
            T_SHA2_16_79!Word(50, W, G, H, A, B, C, D, E, F, constants[50]);
            T_SHA2_16_79!Word(51, W, F, G, H, A, B, C, D, E, constants[51]);
            T_SHA2_16_79!Word(52, W, E, F, G, H, A, B, C, D, constants[52]);
            T_SHA2_16_79!Word(53, W, D, E, F, G, H, A, B, C, constants[53]);
            T_SHA2_16_79!Word(54, W, C, D, E, F, G, H, A, B, constants[54]);
            T_SHA2_16_79!Word(55, W, B, C, D, E, F, G, H, A, constants[55]);
            T_SHA2_16_79!Word(56, W, A, B, C, D, E, F, G, H, constants[56]);
            T_SHA2_16_79!Word(57, W, H, A, B, C, D, E, F, G, constants[57]);
            T_SHA2_16_79!Word(58, W, G, H, A, B, C, D, E, F, constants[58]);
            T_SHA2_16_79!Word(59, W, F, G, H, A, B, C, D, E, constants[59]);
            T_SHA2_16_79!Word(60, W, E, F, G, H, A, B, C, D, constants[60]);
            T_SHA2_16_79!Word(61, W, D, E, F, G, H, A, B, C, constants[61]);
            T_SHA2_16_79!Word(62, W, C, D, E, F, G, H, A, B, constants[62]);
            T_SHA2_16_79!Word(63, W, B, C, D, E, F, G, H, A, constants[63]);

            static if (is(Word == ulong))
            {
                T_SHA2_16_79!Word(64, W, A, B, C, D, E, F, G, H, constants[64]);
                T_SHA2_16_79!Word(65, W, H, A, B, C, D, E, F, G, constants[65]);
                T_SHA2_16_79!Word(66, W, G, H, A, B, C, D, E, F, constants[66]);
                T_SHA2_16_79!Word(67, W, F, G, H, A, B, C, D, E, constants[67]);
                T_SHA2_16_79!Word(68, W, E, F, G, H, A, B, C, D, constants[68]);
                T_SHA2_16_79!Word(69, W, D, E, F, G, H, A, B, C, constants[69]);
                T_SHA2_16_79!Word(70, W, C, D, E, F, G, H, A, B, constants[70]);
                T_SHA2_16_79!Word(71, W, B, C, D, E, F, G, H, A, constants[71]);
                T_SHA2_16_79!Word(72, W, A, B, C, D, E, F, G, H, constants[72]);
                T_SHA2_16_79!Word(73, W, H, A, B, C, D, E, F, G, constants[73]);
                T_SHA2_16_79!Word(74, W, G, H, A, B, C, D, E, F, constants[74]);
                T_SHA2_16_79!Word(75, W, F, G, H, A, B, C, D, E, constants[75]);
                T_SHA2_16_79!Word(76, W, E, F, G, H, A, B, C, D, constants[76]);
                T_SHA2_16_79!Word(77, W, D, E, F, G, H, A, B, C, constants[77]);
                T_SHA2_16_79!Word(78, W, C, D, E, F, G, H, A, B, constants[78]);
                T_SHA2_16_79!Word(79, W, B, C, D, E, F, G, H, A, constants[79]);
            }

            (*state)[0] += A;
            (*state)[1] += B;
            (*state)[2] += C;
            (*state)[3] += D;
            (*state)[4] += E;
            (*state)[5] += F;
            (*state)[6] += G;
            (*state)[7] += H;

            /* Zeroize sensitive information. */
            W[] = 0;
        }

public:
    /**
    * SHA initialization. Begins an SHA1/SHA2 operation.
    *
    * Note:
    * For this SHA Digest implementation calling start after default construction
    * is not necessary. Calling start is only necessary to reset the Digest.
    *
    * Generic code which deals with different Digest types should always call start though.
    *
    * Example:
    * --------
    * SHA1 digest;
    * //digest.start(); //Not necessary
    * digest.put(0);
    * --------
    */
    void start() @safe pure nothrow @nogc
    {
        this = typeof(this).init;
    }

    /**
    * Use this to feed the digest with data.
    * Also implements the $(REF isOutputRange, std,range,primitives)
    * interface for `ubyte` and `const(ubyte)[]`.
    */
    void put(scope const(ubyte)[] input...) @trusted pure nothrow @nogc
    {
        enum blockSizeInBytes = blockSize/8;

        size_t i;
        uint index, partLen;
        auto inputLen = input.length;

        /* Compute number of bytes mod block size (64 or 128 bytes) */
        index = (cast(uint) count[0] >> 3) & (blockSizeInBytes - 1);

        /* Update number of bits */
        static if (blockSize == 512)
            count[0] += inputLen * 8;
        else static if (blockSize == 1024)
        {
            /* ugly hack to work around lack of ucent */
            auto oldCount0 = count[0];
            count[0] += inputLen * 8;
            if (count[0] < oldCount0)
                count[1]++;
        }
        else
            static assert(0);

        partLen = blockSizeInBytes - index;

        /* Transform as many times as possible. */
        if (inputLen >= partLen)
        {
            (&buffer[index])[0 .. partLen] = input.ptr[0 .. partLen];
            transform (&state, &buffer);

            for (i = partLen; i + blockSizeInBytes-1 < inputLen; i += blockSizeInBytes)
                transform(&state, cast(ubyte[blockSizeInBytes]*)(input.ptr + i));

            index = 0;
        }
        else
            i = 0;

        /* Buffer remaining input */
        if (inputLen - i)
            (&buffer[index])[0 .. inputLen-i] = (&input[i])[0 .. inputLen-i];
    }

    @safe unittest
    {
        typeof(this) dig;
        dig.put(cast(ubyte) 0); //single ubyte
        dig.put(cast(ubyte) 0, cast(ubyte) 0); //variadic
        ubyte[10] buf;
        dig.put(buf); //buffer
    }


    /**
    * Returns the finished SHA hash. This also calls $(LREF start) to
    * reset the internal state.
    */
    ubyte[digestSize/8] finish() @trusted pure nothrow @nogc
    {
        static if (blockSize == 512)
        {
            uint[8] data = void;
            uint index, padLen;

            /* Save number of bits */
            ulong bits = nativeToBigEndian(count[0]);

            /* Pad out to 56 mod 64. */
            index = (cast(uint) count[0] >> 3) & (64 - 1);
            padLen = (index < 56) ? (56 - index) : (120 - index);
            put(padding[0 .. padLen]);

            /* Append length (before padding) */
            put((cast(ubyte*) &bits)[0 .. bits.sizeof]);

            /* Store state in digest */
            foreach (i; 0 .. (digestSize == 160) ? 5 : 8)
                data[i] = nativeToBigEndian(state[i]);

            /* Zeroize sensitive information. */
            start();
            return (cast(ubyte*) data.ptr)[0 .. digestSize/8];
        }
        else static if (blockSize == 1024)
        {
            ulong[8] data = void;
            uint index, padLen;

            /* Save number of bits */
            ulong[2] bits = [nativeToBigEndian(count[1]), nativeToBigEndian(count[0])];

            /* Pad out to 112 mod 128. */
            index = (cast(uint) count[0] >> 3) & (128 - 1);
            padLen = (index < 112) ? (112 - index) : (240 - index);
            put(padding[0 .. padLen]);

            /* Append length (before padding) */
            put((cast(ubyte*) &bits)[0 .. bits.sizeof]);

            /* Store state in digest */
            foreach (i; 0 .. 8)
                data[i] = nativeToBigEndian(state[i]);

            /* Zeroize sensitive information. */
            start();
            return (cast(ubyte*) data.ptr)[0 .. digestSize/8];
        }
        else
            static assert(0);
    }
}

unittest
{
    // only instantiate what is actually used

    alias SHA1 = SHA!(512, 160);  /// SHA alias for SHA-1, hash is ubyte[20]
    alias SHA224 = SHA!(512, 224);  /// SHA alias for SHA-224, hash is ubyte[28]
    alias SHA256 = SHA!(512, 256);  /// SHA alias for SHA-256, hash is ubyte[32]
    alias SHA384 = SHA!(1024, 384); /// SHA alias for SHA-384, hash is ubyte[48]
    alias SHA512 = SHA!(1024, 512); /// SHA alias for SHA-512, hash is ubyte[64]
    alias SHA512_224 = SHA!(1024, 224); /// SHA alias for SHA-512/224, hash is ubyte[28]
    alias SHA512_256 = SHA!(1024, 256); /// SHA alias for SHA-512/256, hash is ubyte[32]

    // not all bootstrap compiler versions allow hex string
    int nibble(char ch)
    {
        return ch >= '0' && ch <= '9' ? ch - '0'
            : ch >= 'a' && ch <= 'f' ? ch - 'a' + 10
            : ch >= 'A' && ch <= 'F' ? ch - 'A' + 10 : 0;
    }
    ubyte[] hexString(string s)
    {
        ubyte[] hex = new ubyte[s.length / 2];
        for (size_t i = 0; i < hex.length; i++)
            hex[i] = cast(ubyte)((nibble(s[2 * i]) << 4) | nibble(s[2 * i + 1]));
        return hex;
    }

    SHA1 sha;
    sha.put(cast(ubyte[])"abcdef");
    sha.start();
    sha.put(cast(ubyte[])"");
    assert(sha.finish() == hexString("da39a3ee5e6b4b0d3255bfef95601890afd80709"));

    SHA224 sha224;
    sha224.put(cast(ubyte[])"abcdef");
    sha224.start();
    sha224.put(cast(ubyte[])"");
    assert(sha224.finish() == hexString("d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f"));

    SHA256 sha256;
    sha256.put(cast(ubyte[])"abcdef");
    sha256.start();
    sha256.put(cast(ubyte[])"");
    assert(sha256.finish() ==
           hexString("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"));

    SHA384 sha384;
    sha384.put(cast(ubyte[])"abcdef");
    sha384.start();
    sha384.put(cast(ubyte[])"");
    assert(sha384.finish() ==
           hexString("38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c"
                    ~"0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b"));

    SHA512 sha512;
    sha512.put(cast(ubyte[])"abcdef");
    sha512.start();
    sha512.put(cast(ubyte[])"");
    assert(sha512.finish() ==
           hexString("cf83e1357eefb8bdf1542850d66d8007d620e4050b571"
                    ~"5dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"));

    SHA512_224 sha512_224;
    sha512_224.put(cast(ubyte[])"abcdef");
    sha512_224.start();
    sha512_224.put(cast(ubyte[])"");
    assert(sha512_224.finish() ==
           hexString("6ed0dd02806fa89e25de060c19d3ac86cabb87d6a0ddd05c333b84f4"));

    SHA512_256 sha512_256;
    sha512_256.put(cast(ubyte[])"abcdef");
    sha512_256.start();
    sha512_256.put(cast(ubyte[])"");
    assert(sha512_256.finish() ==
           hexString("c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a"));
}
