Livt packages

Livt.Crypto

Version 1.0.5

Fixed-size cryptographic components for Livt, including AES, ChaCha20, SHA, MACs, and key derivation.

Published files (69)

src/hash/Sha256.lvt

namespace Livt.Crypto.Hash

using Livt.Lang.Annotations

// SHA-256 (NIST FIPS 180-4) — single-block, 512-bit input / 256-bit output.
//
// Usage (per message, one 512-bit block at a time):
//   1. Reset()                    — initialise H0..H7 to FIPS initial values
//   2. ProcessBlock(block)        — compress one 64-byte block
//      Repeat 2 for each block.
//   3. GetDigest() byte[32]       — return current 256-bit digest
//
// Caller is responsible for SHA-256 padding (append 0x80, zeros, 64-bit big-endian
// bit-length) before calling ProcessBlock.
component Sha256
{
    public const BLOCK_SIZE:  int = 64
    public const DIGEST_SIZE: int = 32

    h: byte[32]    // H0..H7: 8 × 4 bytes big-endian
    W: byte[256]   // Message schedule: 64 words × 4 bytes big-endian

    // SHA-256 round constants K[0..63] split by byte position (big-endian).
    // K0[i] = MSByte(K[i]),  K1[i] = byte 1,  K2[i] = byte 2,  K3[i] = LSByte(K[i]).
    const K0: byte[64] = [
        0x42, 0x71, 0xb5, 0xe9, 0x39, 0x59, 0x92, 0xab,
        0xd8, 0x12, 0x24, 0x55, 0x72, 0x80, 0x9b, 0xc1,
        0xe4, 0xef, 0x0f, 0x24, 0x2d, 0x4a, 0x5c, 0x76,
        0x98, 0xa8, 0xb0, 0xbf, 0xc6, 0xd5, 0x06, 0x14,
        0x27, 0x2e, 0x4d, 0x53, 0x65, 0x76, 0x81, 0x92,
        0xa2, 0xa8, 0xc2, 0xc7, 0xd1, 0xd6, 0xf4, 0x10,
        0x19, 0x1e, 0x27, 0x34, 0x39, 0x4e, 0x5b, 0x68,
        0x74, 0x78, 0x84, 0x8c, 0x90, 0xa4, 0xbe, 0xc6]
    const K1: byte[64] = [
        0x8a, 0x37, 0xc0, 0xb5, 0x56, 0xf1, 0x3f, 0x1c,
        0x07, 0x83, 0x31, 0x0c, 0xbe, 0xde, 0xdc, 0x9b,
        0x9b, 0xbe, 0xc1, 0x0c, 0xe9, 0x74, 0xb0, 0xf9,
        0x3e, 0x31, 0x03, 0x59, 0xe0, 0xa7, 0xca, 0x29,
        0xb7, 0x1b, 0x2c, 0x38, 0x0a, 0x6a, 0xc2, 0x72,
        0xbf, 0x1a, 0x4b, 0x6c, 0x92, 0x99, 0x0e, 0x6a,
        0xa4, 0x37, 0x48, 0xb0, 0x1c, 0xd8, 0x9c, 0x2e,
        0x8f, 0xa5, 0xc8, 0xc7, 0xbe, 0x50, 0xf9, 0x71]
    const K2: byte[64] = [
        0x2f, 0x44, 0xfb, 0xdb, 0xc2, 0x11, 0x82, 0x5e,
        0xaa, 0x5b, 0x85, 0x7d, 0x5d, 0xb1, 0x06, 0xf1,
        0x69, 0x47, 0x9d, 0xa1, 0x2c, 0x84, 0xa9, 0x88,
        0x51, 0xc6, 0x27, 0x7f, 0x0b, 0x91, 0x63, 0x29,
        0x0a, 0x21, 0x6d, 0x0d, 0x73, 0x0a, 0xc9, 0x2c,
        0xe8, 0x66, 0x8b, 0x51, 0xe8, 0x06, 0x35, 0xa0,
        0xc1, 0x6c, 0x77, 0xbc, 0x0c, 0xaa, 0xca, 0x6f,
        0x82, 0x63, 0x78, 0x02, 0xff, 0x6c, 0xa3, 0x78]
    const K3: byte[64] = [
        0x98, 0x91, 0xcf, 0xa5, 0x5b, 0xf1, 0xa4, 0xd5,
        0x98, 0x01, 0xbe, 0xc3, 0x74, 0xfe, 0xa7, 0x74,
        0xc1, 0x86, 0xc6, 0xcc, 0x6f, 0xaa, 0xdc, 0xda,
        0x52, 0x6d, 0xc8, 0xc7, 0xf3, 0x47, 0x51, 0x67,
        0x85, 0x38, 0xfc, 0x13, 0x54, 0xbb, 0x2e, 0x85,
        0xa1, 0x4b, 0x70, 0xa3, 0x19, 0x24, 0x85, 0x70,
        0x16, 0x08, 0x4c, 0xb5, 0xb3, 0x4a, 0x4f, 0xf3,
        0xee, 0x6f, 0x14, 0x08, 0xfa, 0xeb, 0xf7, 0xf2]

    new()
    {
    }

    // -----------------------------------------------------------------------
    // Public API
    // -----------------------------------------------------------------------

    // Reset initialises H0..H7 to SHA-256 initial hash values (FIPS 180-4 §5.3.3).
    public fn Reset()
    {
        var init: byte[32] = [
            0x6a, 0x09, 0xe6, 0x67,
            0xbb, 0x67, 0xae, 0x85,
            0x3c, 0x6e, 0xf3, 0x72,
            0xa5, 0x4f, 0xf5, 0x3a,
            0x51, 0x0e, 0x52, 0x7f,
            0x9b, 0x05, 0x68, 0x8c,
            0x1f, 0x83, 0xd9, 0xab,
            0x5b, 0xe0, 0xcd, 0x19]
        for (var i = 0; i < 32; i++)
        {
            this.h[i] = init[i]
        }
    }

    // ProcessBlock compresses one 64-byte (512-bit) message block.
    // Call Reset() before the first block. For multi-block messages call
    // ProcessBlock once per block; do NOT call Reset() between blocks.
    public fn ProcessBlock(msg: byte[64])
    {
        // Initialise working variables from current hash state.
        var ha0: byte = this.h[0];  var ha1: byte = this.h[1];  var ha2: byte = this.h[2];  var ha3: byte = this.h[3]
        var hb0: byte = this.h[4];  var hb1: byte = this.h[5];  var hb2: byte = this.h[6];  var hb3: byte = this.h[7]
        var hc0: byte = this.h[8];  var hc1: byte = this.h[9];  var hc2: byte = this.h[10]; var hc3: byte = this.h[11]
        var hd0: byte = this.h[12]; var hd1: byte = this.h[13]; var hd2: byte = this.h[14]; var hd3: byte = this.h[15]
        var he0: byte = this.h[16]; var he1: byte = this.h[17]; var he2: byte = this.h[18]; var he3: byte = this.h[19]
        var hf0: byte = this.h[20]; var hf1: byte = this.h[21]; var hf2: byte = this.h[22]; var hf3: byte = this.h[23]
        var hg0: byte = this.h[24]; var hg1: byte = this.h[25]; var hg2: byte = this.h[26]; var hg3: byte = this.h[27]
        var hh0: byte = this.h[28]; var hh1: byte = this.h[29]; var hh2: byte = this.h[30]; var hh3: byte = this.h[31]
        var a0: byte = ha0; var a1: byte = ha1; var a2: byte = ha2; var a3: byte = ha3
        var b0: byte = hb0; var b1: byte = hb1; var b2: byte = hb2; var b3: byte = hb3
        var c0: byte = hc0; var c1: byte = hc1; var c2: byte = hc2; var c3: byte = hc3
        var d0: byte = hd0; var d1: byte = hd1; var d2: byte = hd2; var d3: byte = hd3
        var e0: byte = he0; var e1: byte = he1; var e2: byte = he2; var e3: byte = he3
        var f0: byte = hf0; var f1: byte = hf1; var f2: byte = hf2; var f3: byte = hf3
        var g0: byte = hg0; var g1: byte = hg1; var g2: byte = hg2; var g3: byte = hg3
        var wh0: byte = hh0; var wh1: byte = hh1; var wh2: byte = hh2; var wh3: byte = hh3

        // Copy first 16 words (64 bytes) from block into this.W[0..63].
        this.W[0]  = msg[0];  this.W[1]  = msg[1];  this.W[2]  = msg[2];  this.W[3]  = msg[3]
        this.W[4]  = msg[4];  this.W[5]  = msg[5];  this.W[6]  = msg[6];  this.W[7]  = msg[7]
        this.W[8]  = msg[8];  this.W[9]  = msg[9];  this.W[10] = msg[10]; this.W[11] = msg[11]
        this.W[12] = msg[12]; this.W[13] = msg[13]; this.W[14] = msg[14]; this.W[15] = msg[15]
        this.W[16] = msg[16]; this.W[17] = msg[17]; this.W[18] = msg[18]; this.W[19] = msg[19]
        this.W[20] = msg[20]; this.W[21] = msg[21]; this.W[22] = msg[22]; this.W[23] = msg[23]
        this.W[24] = msg[24]; this.W[25] = msg[25]; this.W[26] = msg[26]; this.W[27] = msg[27]
        this.W[28] = msg[28]; this.W[29] = msg[29]; this.W[30] = msg[30]; this.W[31] = msg[31]
        this.W[32] = msg[32]; this.W[33] = msg[33]; this.W[34] = msg[34]; this.W[35] = msg[35]
        this.W[36] = msg[36]; this.W[37] = msg[37]; this.W[38] = msg[38]; this.W[39] = msg[39]
        this.W[40] = msg[40]; this.W[41] = msg[41]; this.W[42] = msg[42]; this.W[43] = msg[43]
        this.W[44] = msg[44]; this.W[45] = msg[45]; this.W[46] = msg[46]; this.W[47] = msg[47]
        this.W[48] = msg[48]; this.W[49] = msg[49]; this.W[50] = msg[50]; this.W[51] = msg[51]
        this.W[52] = msg[52]; this.W[53] = msg[53]; this.W[54] = msg[54]; this.W[55] = msg[55]
        this.W[56] = msg[56]; this.W[57] = msg[57]; this.W[58] = msg[58]; this.W[59] = msg[59]
        this.W[60] = msg[60]; this.W[61] = msg[61]; this.W[62] = msg[62]; this.W[63] = msg[63]

        // Extend message schedule W[16..63].
        // Reads use indices < j (written by prior iterations or the copy above).
        for (var j = 16; j < 64; j++)
        {
            var jm2:  int = (j - 2)  * 4
            var jm7:  int = (j - 7)  * 4
            var jm15: int = (j - 15) * 4
            var jm16: int = (j - 16) * 4
            var jm2p1:  int = jm2  + 1
            var jm2p2:  int = jm2  + 2
            var jm2p3:  int = jm2  + 3
            var jm7p1:  int = jm7  + 1
            var jm7p2:  int = jm7  + 2
            var jm7p3:  int = jm7  + 3
            var jm15p1: int = jm15 + 1
            var jm15p2: int = jm15 + 2
            var jm15p3: int = jm15 + 3
            var jm16p1: int = jm16 + 1
            var jm16p2: int = jm16 + 2
            var jm16p3: int = jm16 + 3

            var wm2b0:  byte = this.W[jm2];   var wm2b1:  byte = this.W[jm2p1]
            var wm2b2:  byte = this.W[jm2p2]; var wm2b3:  byte = this.W[jm2p3]
            var wm7b0:  byte = this.W[jm7];   var wm7b1:  byte = this.W[jm7p1]
            var wm7b2:  byte = this.W[jm7p2]; var wm7b3:  byte = this.W[jm7p3]
            var wm15b0: byte = this.W[jm15];  var wm15b1: byte = this.W[jm15p1]
            var wm15b2: byte = this.W[jm15p2];var wm15b3: byte = this.W[jm15p3]
            var wm16b0: byte = this.W[jm16];  var wm16b1: byte = this.W[jm16p1]
            var wm16b2: byte = this.W[jm16p2];var wm16b3: byte = this.W[jm16p3]

            var ls1: byte[4] = this.LilSigma1(wm2b0, wm2b1, wm2b2, wm2b3)
            var ls1b0: byte = ls1[0]; var ls1b1: byte = ls1[1]; var ls1b2: byte = ls1[2]; var ls1b3: byte = ls1[3]
            var ls0: byte[4] = this.LilSigma0(wm15b0, wm15b1, wm15b2, wm15b3)
            var ls0b0: byte = ls0[0]; var ls0b1: byte = ls0[1]; var ls0b2: byte = ls0[2]; var ls0b3: byte = ls0[3]
            var s1: byte[4] = this.AddWord(ls1b0, ls1b1, ls1b2, ls1b3, wm7b0, wm7b1, wm7b2, wm7b3)
            var s1b0: byte = s1[0]; var s1b1: byte = s1[1]; var s1b2: byte = s1[2]; var s1b3: byte = s1[3]
            var s2: byte[4] = this.AddWord(s1b0, s1b1, s1b2, s1b3, ls0b0, ls0b1, ls0b2, ls0b3)
            var s2b0: byte = s2[0]; var s2b1: byte = s2[1]; var s2b2: byte = s2[2]; var s2b3: byte = s2[3]
            var wj: byte[4] = this.AddWord(s2b0, s2b1, s2b2, s2b3, wm16b0, wm16b1, wm16b2, wm16b3)
            var wjb0: byte = wj[0]; var wjb1: byte = wj[1]; var wjb2: byte = wj[2]; var wjb3: byte = wj[3]

            var jbase: int = j * 4
            var jbp1:  int = jbase + 1
            var jbp2:  int = jbase + 2
            var jbp3:  int = jbase + 3
            this.W[jbase] = wjb0
            this.W[jbp1]  = wjb1
            this.W[jbp2]  = wjb2
            this.W[jbp3]  = wjb3
        }

        // 64-round compression.
        for (var i = 0; i < 64; i++)
        {
            var ki0: byte = this.K0[i]
            var ki1: byte = this.K1[i]
            var ki2: byte = this.K2[i]
            var ki3: byte = this.K3[i]

            var ibase: int = i * 4
            var ibp1:  int = ibase + 1
            var ibp2:  int = ibase + 2
            var ibp3:  int = ibase + 3
            var w0: byte = this.W[ibase]
            var w1: byte = this.W[ibp1]
            var w2: byte = this.W[ibp2]
            var w3: byte = this.W[ibp3]

            var bsig1e: byte[4] = this.BigSigma1(e0, e1, e2, e3)
            var bsig1e0: byte = bsig1e[0]; var bsig1e1: byte = bsig1e[1]
            var bsig1e2: byte = bsig1e[2]; var bsig1e3: byte = bsig1e[3]

            var cval: byte[4] = this.Ch(e0, e1, e2, e3, f0, f1, f2, f3, g0, g1, g2, g3)
            var cval0: byte = cval[0]; var cval1: byte = cval[1]
            var cval2: byte = cval[2]; var cval3: byte = cval[3]

            // T1 = wh + BigSigma1(e) + Ch(e,f,g) + K[i] + W[i]
            var t1a: byte[4] = this.AddWord(wh0, wh1, wh2, wh3, bsig1e0, bsig1e1, bsig1e2, bsig1e3)
            var t1a0: byte = t1a[0]; var t1a1: byte = t1a[1]; var t1a2: byte = t1a[2]; var t1a3: byte = t1a[3]
            var t1b: byte[4] = this.AddWord(t1a0, t1a1, t1a2, t1a3, cval0, cval1, cval2, cval3)
            var t1b0: byte = t1b[0]; var t1b1: byte = t1b[1]; var t1b2: byte = t1b[2]; var t1b3: byte = t1b[3]
            var t1c: byte[4] = this.AddWord(t1b0, t1b1, t1b2, t1b3, ki0, ki1, ki2, ki3)
            var t1c0: byte = t1c[0]; var t1c1: byte = t1c[1]; var t1c2: byte = t1c[2]; var t1c3: byte = t1c[3]
            var t1:  byte[4] = this.AddWord(t1c0, t1c1, t1c2, t1c3, w0, w1, w2, w3)
            var t10: byte = t1[0]; var t11: byte = t1[1]; var t12: byte = t1[2]; var t13: byte = t1[3]

            var bsig0a: byte[4] = this.BigSigma0(a0, a1, a2, a3)
            var bsig0a0: byte = bsig0a[0]; var bsig0a1: byte = bsig0a[1]
            var bsig0a2: byte = bsig0a[2]; var bsig0a3: byte = bsig0a[3]

            var mval: byte[4] = this.Maj(a0, a1, a2, a3, b0, b1, b2, b3, c0, c1, c2, c3)
            var mval0: byte = mval[0]; var mval1: byte = mval[1]
            var mval2: byte = mval[2]; var mval3: byte = mval[3]

            // T2 = BigSigma0(a) + Maj(a,b,c)
            var t2:  byte[4] = this.AddWord(bsig0a0, bsig0a1, bsig0a2, bsig0a3, mval0, mval1, mval2, mval3)
            var t20: byte = t2[0]; var t21: byte = t2[1]; var t22: byte = t2[2]; var t23: byte = t2[3]

            // Compute new e = d + T1  and  new a = T1 + T2  before shifting.
            var ne: byte[4] = this.AddWord(d0, d1, d2, d3, t10, t11, t12, t13)
            var na: byte[4] = this.AddWord(t10, t11, t12, t13, t20, t21, t22, t23)

            // Rotate: h=g, g=f, f=e, e=ne, d=c, c=b, b=a, a=na
            wh0 = g0; wh1 = g1; wh2 = g2; wh3 = g3
            g0 = f0; g1 = f1; g2 = f2; g3 = f3
            f0 = e0; f1 = e1; f2 = e2; f3 = e3
            e0 = ne[0]; e1 = ne[1]; e2 = ne[2]; e3 = ne[3]
            d0 = c0; d1 = c1; d2 = c2; d3 = c3
            c0 = b0; c1 = b1; c2 = b2; c3 = b3
            b0 = a0; b1 = a1; b2 = a2; b3 = a3
            a0 = na[0]; a1 = na[1]; a2 = na[2]; a3 = na[3]
        }

        // H_new[k] = H_old[k] + working_var[k]
        var nh0: byte[4] = this.AddWord(ha0, ha1, ha2, ha3, a0, a1, a2, a3)
        var nh1: byte[4] = this.AddWord(hb0, hb1, hb2, hb3, b0, b1, b2, b3)
        var nh2: byte[4] = this.AddWord(hc0, hc1, hc2, hc3, c0, c1, c2, c3)
        var nh3: byte[4] = this.AddWord(hd0, hd1, hd2, hd3, d0, d1, d2, d3)
        var nh4: byte[4] = this.AddWord(he0, he1, he2, he3, e0, e1, e2, e3)
        var nh5: byte[4] = this.AddWord(hf0, hf1, hf2, hf3, f0, f1, f2, f3)
        var nh6: byte[4] = this.AddWord(hg0, hg1, hg2, hg3, g0, g1, g2, g3)
        var nh7: byte[4] = this.AddWord(hh0, hh1, hh2, hh3, wh0, wh1, wh2, wh3)

        // Assemble into a local array then write via for loop (§5).
        var newH: byte[32]
        newH[0]  = nh0[0]; newH[1]  = nh0[1]; newH[2]  = nh0[2]; newH[3]  = nh0[3]
        newH[4]  = nh1[0]; newH[5]  = nh1[1]; newH[6]  = nh1[2]; newH[7]  = nh1[3]
        newH[8]  = nh2[0]; newH[9]  = nh2[1]; newH[10] = nh2[2]; newH[11] = nh2[3]
        newH[12] = nh3[0]; newH[13] = nh3[1]; newH[14] = nh3[2]; newH[15] = nh3[3]
        newH[16] = nh4[0]; newH[17] = nh4[1]; newH[18] = nh4[2]; newH[19] = nh4[3]
        newH[20] = nh5[0]; newH[21] = nh5[1]; newH[22] = nh5[2]; newH[23] = nh5[3]
        newH[24] = nh6[0]; newH[25] = nh6[1]; newH[26] = nh6[2]; newH[27] = nh6[3]
        newH[28] = nh7[0]; newH[29] = nh7[1]; newH[30] = nh7[2]; newH[31] = nh7[3]
        for (var j = 0; j < 32; j++)
        {
            this.h[j] = newH[j]
        }
    }

    // GetDigest returns the current 256-bit hash value as 32 bytes (big-endian).
    public fn GetDigest() byte[32]
    {
        return this.h
    }

    // -----------------------------------------------------------------------
    // Private helpers — all pure (no this.* writes except ProcessBlock).
    // -----------------------------------------------------------------------

    // AddWord: modular 32-bit addition in big-endian byte form.
    inline fn AddWord[](a0: byte, a1: byte, a2: byte, a3: byte,
               b0: byte, b1: byte, b2: byte, b3: byte) byte[4]
    {
        var a0i: int = a0 as int; var a1i: int = a1 as int
        var a2i: int = a2 as int; var a3i: int = a3 as int
        var b0i: int = b0 as int; var b1i: int = b1 as int
        var b2i: int = b2 as int; var b3i: int = b3 as int

        var sum3: int = a3i + b3i
        var d3i:  int = sum3 % 256
        var c3:   int = sum3 / 256
        var sum2: int = a2i + b2i + c3
        var d2i:  int = sum2 % 256
        var c2:   int = sum2 / 256
        var sum1: int = a1i + b1i + c2
        var d1i:  int = sum1 % 256
        var c1:   int = sum1 / 256
        var sum0: int = a0i + b0i + c1
        var d0i:  int = sum0 % 256

        var result: byte[4]
        var rb0: byte = d0i as byte
        var rb1: byte = d1i as byte
        var rb2: byte = d2i as byte
        var rb3: byte = d3i as byte
        result[0] = rb0
        result[1] = rb1
        result[2] = rb2
        result[3] = rb3
        return result
    }

    // XorWord: bitwise XOR of two 32-bit words, byte by byte.
    inline fn XorWord[](a0: byte, a1: byte, a2: byte, a3: byte,
               b0: byte, b1: byte, b2: byte, b3: byte) byte[4]
    {
        var x0: byte = a0 ^ b0
        var x1: byte = a1 ^ b1
        var x2: byte = a2 ^ b2
        var x3: byte = a3 ^ b3
        var result: byte[4]
        result[0] = x0; result[1] = x1; result[2] = x2; result[3] = x3
        return result
    }

    // AndWord: bitwise AND of two 32-bit words, byte by byte.
    inline fn AndWord[](a0: byte, a1: byte, a2: byte, a3: byte,
               b0: byte, b1: byte, b2: byte, b3: byte) byte[4]
    {
        var x0: byte = a0 & b0
        var x1: byte = a1 & b1
        var x2: byte = a2 & b2
        var x3: byte = a3 & b3
        var result: byte[4]
        result[0] = x0; result[1] = x1; result[2] = x2; result[3] = x3
        return result
    }

    // Rotr: rotate right a 32-bit big-endian word by n bits (0 <= n <= 31).
    @SuppressWarning("variable-divisor")
    inline fn Rotr[](b0: byte, b1: byte, b2: byte, b3: byte, n: int) byte[4]
    {
        var q: int = n / 8
        var r: int = n % 8

        // Byte-level rotation: shift bytes right by q positions (circular).
        var c0: byte = b0; var c1: byte = b1; var c2: byte = b2; var c3: byte = b3
        if (q == 1)
        {
            c0 = b3; c1 = b0; c2 = b1; c3 = b2
        }
        if (q == 2)
        {
            c0 = b2; c1 = b3; c2 = b0; c3 = b1
        }
        if (q == 3)
        {
            c0 = b1; c1 = b2; c2 = b3; c3 = b0
        }

        // Bit-level rotation right by r bits within the 4 bytes.
        var pow_r: int = 1
        if (r >= 1) { pow_r = 2 }
        if (r >= 2) { pow_r = 4 }
        if (r >= 3) { pow_r = 8 }
        if (r >= 4) { pow_r = 16 }
        if (r >= 5) { pow_r = 32 }
        if (r >= 6) { pow_r = 64 }
        if (r >= 7) { pow_r = 128 }
        var pow_8mr: int = 256 / pow_r

        var c0i: int = c0 as int; var c1i: int = c1 as int
        var c2i: int = c2 as int; var c3i: int = c3 as int

        // Each byte shifts right r bits; the r low bits wrap from the PREVIOUS byte.
        var d0i: int = c0i / pow_r + (c3i % pow_r) * pow_8mr
        var d1i: int = c1i / pow_r + (c0i % pow_r) * pow_8mr
        var d2i: int = c2i / pow_r + (c1i % pow_r) * pow_8mr
        var d3i: int = c3i / pow_r + (c2i % pow_r) * pow_8mr

        var result: byte[4]
        var rr0: byte = d0i as byte
        var rr1: byte = d1i as byte
        var rr2: byte = d2i as byte
        var rr3: byte = d3i as byte
        result[0] = rr0
        result[1] = rr1
        result[2] = rr2
        result[3] = rr3
        return result
    }

    // Shr: logical right shift of a 32-bit big-endian word by n bits (0 <= n <= 31).
    @SuppressWarning("variable-divisor")
    inline fn Shr[](b0: byte, b1: byte, b2: byte, b3: byte, n: int) byte[4]
    {
        var q: int = n / 8
        var r: int = n % 8

        // Byte-level shift: shift bytes right by q positions, fill MSBytes with 0.
        var c0: byte = b0; var c1: byte = b1; var c2: byte = b2; var c3: byte = b3
        if (q == 1)
        {
            c0 = 0x00; c1 = b0; c2 = b1; c3 = b2
        }
        if (q == 2)
        {
            c0 = 0x00; c1 = 0x00; c2 = b0; c3 = b1
        }
        if (q == 3)
        {
            c0 = 0x00; c1 = 0x00; c2 = 0x00; c3 = b0
        }

        // Bit-level shift right by r bits; MSBits of d0 are 0 (no wrap from c3).
        var pow_r: int = 1
        if (r >= 1) { pow_r = 2 }
        if (r >= 2) { pow_r = 4 }
        if (r >= 3) { pow_r = 8 }
        if (r >= 4) { pow_r = 16 }
        if (r >= 5) { pow_r = 32 }
        if (r >= 6) { pow_r = 64 }
        if (r >= 7) { pow_r = 128 }
        var pow_8mr: int = 256 / pow_r

        var c0i: int = c0 as int; var c1i: int = c1 as int
        var c2i: int = c2 as int; var c3i: int = c3 as int

        var d0i: int = c0i / pow_r
        var d1i: int = c1i / pow_r + (c0i % pow_r) * pow_8mr
        var d2i: int = c2i / pow_r + (c1i % pow_r) * pow_8mr
        var d3i: int = c3i / pow_r + (c2i % pow_r) * pow_8mr

        var result: byte[4]
        var rs0: byte = d0i as byte
        var rs1: byte = d1i as byte
        var rs2: byte = d2i as byte
        var rs3: byte = d3i as byte
        result[0] = rs0
        result[1] = rs1
        result[2] = rs2
        result[3] = rs3
        return result
    }

    // BigSigma0: ROTR_2 XOR ROTR_13 XOR ROTR_22  (used for working variable a).
    fn BigSigma0[](b0: byte, b1: byte, b2: byte, b3: byte) byte[4]
    {
        var r2:  byte[4] = this.Rotr(b0, b1, b2, b3, 2)
        var r2b0:  byte = r2[0];  var r2b1:  byte = r2[1];  var r2b2:  byte = r2[2];  var r2b3:  byte = r2[3]
        var r13: byte[4] = this.Rotr(b0, b1, b2, b3, 13)
        var r13b0: byte = r13[0]; var r13b1: byte = r13[1]; var r13b2: byte = r13[2]; var r13b3: byte = r13[3]
        var r22: byte[4] = this.Rotr(b0, b1, b2, b3, 22)
        var r22b0: byte = r22[0]; var r22b1: byte = r22[1]; var r22b2: byte = r22[2]; var r22b3: byte = r22[3]
        var x1: byte[4] = this.XorWord(r2b0, r2b1, r2b2, r2b3, r13b0, r13b1, r13b2, r13b3)
        var x1b0: byte = x1[0]; var x1b1: byte = x1[1]; var x1b2: byte = x1[2]; var x1b3: byte = x1[3]
        var result: byte[4] = this.XorWord(x1b0, x1b1, x1b2, x1b3, r22b0, r22b1, r22b2, r22b3)
        return result
    }

    // BigSigma1: ROTR_6 XOR ROTR_11 XOR ROTR_25  (used for working variable e).
    fn BigSigma1[](b0: byte, b1: byte, b2: byte, b3: byte) byte[4]
    {
        var r6:  byte[4] = this.Rotr(b0, b1, b2, b3, 6)
        var r6b0:  byte = r6[0];  var r6b1:  byte = r6[1];  var r6b2:  byte = r6[2];  var r6b3:  byte = r6[3]
        var r11: byte[4] = this.Rotr(b0, b1, b2, b3, 11)
        var r11b0: byte = r11[0]; var r11b1: byte = r11[1]; var r11b2: byte = r11[2]; var r11b3: byte = r11[3]
        var r25: byte[4] = this.Rotr(b0, b1, b2, b3, 25)
        var r25b0: byte = r25[0]; var r25b1: byte = r25[1]; var r25b2: byte = r25[2]; var r25b3: byte = r25[3]
        var x1: byte[4] = this.XorWord(r6b0, r6b1, r6b2, r6b3, r11b0, r11b1, r11b2, r11b3)
        var x1b0: byte = x1[0]; var x1b1: byte = x1[1]; var x1b2: byte = x1[2]; var x1b3: byte = x1[3]
        var result: byte[4] = this.XorWord(x1b0, x1b1, x1b2, x1b3, r25b0, r25b1, r25b2, r25b3)
        return result
    }

    // LilSigma0: ROTR_7 XOR ROTR_18 XOR SHR_3  (message schedule σ0).
    fn LilSigma0[](b0: byte, b1: byte, b2: byte, b3: byte) byte[4]
    {
        var r7:  byte[4] = this.Rotr(b0, b1, b2, b3, 7)
        var r7b0:  byte = r7[0];  var r7b1:  byte = r7[1];  var r7b2:  byte = r7[2];  var r7b3:  byte = r7[3]
        var r18: byte[4] = this.Rotr(b0, b1, b2, b3, 18)
        var r18b0: byte = r18[0]; var r18b1: byte = r18[1]; var r18b2: byte = r18[2]; var r18b3: byte = r18[3]
        var s3:  byte[4] = this.Shr(b0, b1, b2, b3, 3)
        var s3b0:  byte = s3[0];  var s3b1:  byte = s3[1];  var s3b2:  byte = s3[2];  var s3b3:  byte = s3[3]
        var x1: byte[4] = this.XorWord(r7b0, r7b1, r7b2, r7b3, r18b0, r18b1, r18b2, r18b3)
        var x1b0: byte = x1[0]; var x1b1: byte = x1[1]; var x1b2: byte = x1[2]; var x1b3: byte = x1[3]
        var result: byte[4] = this.XorWord(x1b0, x1b1, x1b2, x1b3, s3b0, s3b1, s3b2, s3b3)
        return result
    }

    // LilSigma1: ROTR_17 XOR ROTR_19 XOR SHR_10  (message schedule σ1).
    fn LilSigma1[](b0: byte, b1: byte, b2: byte, b3: byte) byte[4]
    {
        var r17: byte[4] = this.Rotr(b0, b1, b2, b3, 17)
        var r17b0: byte = r17[0]; var r17b1: byte = r17[1]; var r17b2: byte = r17[2]; var r17b3: byte = r17[3]
        var r19: byte[4] = this.Rotr(b0, b1, b2, b3, 19)
        var r19b0: byte = r19[0]; var r19b1: byte = r19[1]; var r19b2: byte = r19[2]; var r19b3: byte = r19[3]
        var s10: byte[4] = this.Shr(b0, b1, b2, b3, 10)
        var s10b0: byte = s10[0]; var s10b1: byte = s10[1]; var s10b2: byte = s10[2]; var s10b3: byte = s10[3]
        var x1: byte[4] = this.XorWord(r17b0, r17b1, r17b2, r17b3, r19b0, r19b1, r19b2, r19b3)
        var x1b0: byte = x1[0]; var x1b1: byte = x1[1]; var x1b2: byte = x1[2]; var x1b3: byte = x1[3]
        var result: byte[4] = this.XorWord(x1b0, x1b1, x1b2, x1b3, s10b0, s10b1, s10b2, s10b3)
        return result
    }

    // Ch: (e AND f) XOR ((NOT e) AND g)
    fn Ch[](e0: byte, e1: byte, e2: byte, e3: byte,
          f0: byte, f1: byte, f2: byte, f3: byte,
          g0: byte, g1: byte, g2: byte, g3: byte) byte[4]
    {
        var ef: byte[4] = this.AndWord(e0, e1, e2, e3, f0, f1, f2, f3)
        var ef0: byte = ef[0]; var ef1: byte = ef[1]; var ef2: byte = ef[2]; var ef3: byte = ef[3]

        // NOT(e) byte-by-byte: 255 - e
        var e0i: int = e0 as int
        var ne0i: int = 255 - e0i
        var ne0: byte = ne0i as byte
        var e1i: int = e1 as int
        var ne1i: int = 255 - e1i
        var ne1: byte = ne1i as byte
        var e2i: int = e2 as int
        var ne2i: int = 255 - e2i
        var ne2: byte = ne2i as byte
        var e3i: int = e3 as int
        var ne3i: int = 255 - e3i
        var ne3: byte = ne3i as byte

        var neg: byte[4] = this.AndWord(ne0, ne1, ne2, ne3, g0, g1, g2, g3)
        var neg0: byte = neg[0]; var neg1: byte = neg[1]; var neg2: byte = neg[2]; var neg3: byte = neg[3]

        var result: byte[4] = this.XorWord(ef0, ef1, ef2, ef3, neg0, neg1, neg2, neg3)
        return result
    }

    // Maj: (a AND b) XOR (a AND c) XOR (b AND c)
    fn Maj[](a0: byte, a1: byte, a2: byte, a3: byte,
           b0: byte, b1: byte, b2: byte, b3: byte,
           c0: byte, c1: byte, c2: byte, c3: byte) byte[4]
    {
        var ab: byte[4] = this.AndWord(a0, a1, a2, a3, b0, b1, b2, b3)
        var ab0: byte = ab[0]; var ab1: byte = ab[1]; var ab2: byte = ab[2]; var ab3: byte = ab[3]
        var ac: byte[4] = this.AndWord(a0, a1, a2, a3, c0, c1, c2, c3)
        var ac0: byte = ac[0]; var ac1: byte = ac[1]; var ac2: byte = ac[2]; var ac3: byte = ac[3]
        var bc: byte[4] = this.AndWord(b0, b1, b2, b3, c0, c1, c2, c3)
        var bc0: byte = bc[0]; var bc1: byte = bc[1]; var bc2: byte = bc[2]; var bc3: byte = bc[3]
        var x1: byte[4] = this.XorWord(ab0, ab1, ab2, ab3, ac0, ac1, ac2, ac3)
        var x1b0: byte = x1[0]; var x1b1: byte = x1[1]; var x1b2: byte = x1[2]; var x1b3: byte = x1[3]
        var result: byte[4] = this.XorWord(x1b0, x1b1, x1b2, x1b3, bc0, bc1, bc2, bc3)
        return result
    }
}