-- SHA-256 hash library for Lua 5.1 -- Pure Lua implementation: 32-bit arithmetic emulation, no external dependencies. local sha256 = {} local MOD = 4294967296 -- 2^32 --------------------------------------------------------------------- -- 32-bit unsigned integer operations --------------------------------------------------------------------- local function rshift(x, n) return math.floor((x % MOD) / 2 ^ n) end local function lshift(x, n) x = x % MOD if n >= 32 then return 0 end if n == 0 then return x end local lo = x % 65536 local hi = math.floor(x / 65536) if n < 16 then return ((lo * 2 ^ n) + ((hi * 2 ^ n) % 65536) * 65536) % MOD else -- hi part vanishes modulo 2^32; lo part stays exact because n-16 ≤ 14 return lo * 2 ^ (n - 16) * 65536 end end local function rrotate(x, n) x = x % MOD n = n % 32 if n == 0 then return x end return math.floor(x / 2 ^ n) + (x % 2 ^ n) * 2 ^ (32 - n) end local function bxor(a, b) local r, bit = 0, 1 a, b = a % MOD, b % MOD while a > 0 or b > 0 do if a % 2 ~= b % 2 then r = r + bit end bit = bit * 2 a, b = math.floor(a / 2), math.floor(b / 2) end return r end local function band(a, b) local r, bit = 0, 1 a, b = a % MOD, b % MOD while a > 0 and b > 0 do if a % 2 == 1 and b % 2 == 1 then r = r + bit end bit = bit * 2 a, b = math.floor(a / 2), math.floor(b / 2) end return r end local function bnot(x) return MOD - 1 - (x % MOD) end --------------------------------------------------------------------- -- SHA-256 logical functions --------------------------------------------------------------------- local function Ch(x, y, z) return bxor(band(x, y), band(bnot(x), z)) end local function Maj(x, y, z) return bxor(bxor(band(x, y), band(x, z)), band(y, z)) end local function Sigma0(x) return bxor(bxor(rrotate(x, 2), rrotate(x, 13)), rrotate(x, 22)) end local function Sigma1(x) return bxor(bxor(rrotate(x, 6), rrotate(x, 11)), rrotate(x, 25)) end local function sigma0(x) return bxor(bxor(rrotate(x, 7), rrotate(x, 18)), rshift(x, 3)) end local function sigma1(x) return bxor(bxor(rrotate(x, 17), rrotate(x, 19)), rshift(x, 10)) end --------------------------------------------------------------------- -- Constants --------------------------------------------------------------------- local H0 = { 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, } local K = { 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, } --------------------------------------------------------------------- -- Core SHA-256 transform (one 512-bit chunk) --------------------------------------------------------------------- local function transform(chunk, h) local w = {} -- Message schedule: W[0..15] from chunk bytes (big-endian) for i = 0, 15 do local b = i * 4 + 1 w[i] = lshift(chunk[b], 24) + lshift(chunk[b + 1], 16) + lshift(chunk[b + 2], 8) + chunk[b + 3] end -- Message schedule: W[16..63] for i = 16, 63 do w[i] = (sigma1(w[i - 2]) + w[i - 7] + sigma0(w[i - 15]) + w[i - 16]) % MOD end -- Working variables local a, b, c, d, e, f, g, hh = h[1], h[2], h[3], h[4], h[5], h[6], h[7], h[8] -- Compression for i = 0, 63 do local t1 = (hh + Sigma1(e) + Ch(e, f, g) + K[i + 1] + w[i]) % MOD local t2 = (Sigma0(a) + Maj(a, b, c)) % MOD hh, g, f, e, d, c, b, a = g, f, e, (d + t1) % MOD, c, b, a, (t1 + t2) % MOD end -- Update hash state h[1] = (h[1] + a) % MOD h[2] = (h[2] + b) % MOD h[3] = (h[3] + c) % MOD h[4] = (h[4] + d) % MOD h[5] = (h[5] + e) % MOD h[6] = (h[6] + f) % MOD h[7] = (h[7] + g) % MOD h[8] = (h[8] + hh) % MOD end --------------------------------------------------------------------- -- Public API --------------------------------------------------------------------- function sha256.hash(data) -- Convert string to byte array local bytes = {string.byte(data, 1, #data)} local bit_len = #bytes * 8 -- Padding: append 1 bit (0x80), then zero bits, then 64-bit length local pad = {} for i = 1, #bytes do pad[i] = bytes[i] end pad[#pad + 1] = 0x80 while (#pad + 8) % 64 ~= 0 do pad[#pad + 1] = 0 end -- 64-bit message length in big-endian local hi = math.floor(bit_len / MOD) local lo = bit_len % MOD pad[#pad + 1] = rshift(hi, 24) % 256 pad[#pad + 1] = rshift(hi, 16) % 256 pad[#pad + 1] = rshift(hi, 8) % 256 pad[#pad + 1] = hi % 256 pad[#pad + 1] = rshift(lo, 24) % 256 pad[#pad + 1] = rshift(lo, 16) % 256 pad[#pad + 1] = rshift(lo, 8) % 256 pad[#pad + 1] = lo % 256 -- Process each 512-bit chunk local h = {H0[1], H0[2], H0[3], H0[4], H0[5], H0[6], H0[7], H0[8]} for i = 1, #pad, 64 do local chunk = {} for j = 1, 64 do chunk[j] = pad[i + j - 1] end transform(chunk, h) end return string.format("%08x%08x%08x%08x%08x%08x%08x%08x", h[1], h[2], h[3], h[4], h[5], h[6], h[7], h[8]) end -- sha256(data) shorthand setmetatable(sha256, { __call = function(_, data) return sha256.hash(data) end }) return sha256