Skip to content

Instantly share code, notes, and snippets.

@Dekkonot
Created September 19, 2024 01:36
Show Gist options
  • Save Dekkonot/84c0b056d330801ec2a656cb6a27ca08 to your computer and use it in GitHub Desktop.
Save Dekkonot/84c0b056d330801ec2a656cb6a27ca08 to your computer and use it in GitHub Desktop.
--!strict
--!native
--!optimize 2
local BLOCK_FRONT = table.create(80, 0)
local BLOCK_BACK = table.create(80, 0)
--stylua: ignore
local K_FRONT = {
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, 0xca273ece,
0xd186b8c7, 0xeada7dd6, 0xf57d4f7f, 0x06f067aa, 0x0a637dc5,
0x113f9804, 0x1b710b35, 0x28db77f5, 0x32caab7b, 0x3c9ebe0a,
0x431d67c4, 0x4cc5d4be, 0x597f299c, 0x5fcb6fab, 0x6c44198c,
}
--stylua: ignore
local K_BACK = {
0xd728ae22, 0x23ef65cd, 0xec4d3b2f, 0x8189dbbc, 0xf348b538,
0xb605d019, 0xaf194f9b, 0xda6d8118, 0xa3030242, 0x45706fbe,
0x4ee4b28c, 0xd5ffb4e2, 0xf27b896f, 0x3b1696b1, 0x25c71235,
0xcf692694, 0x9ef14ad2, 0x384f25e3, 0x8b8cd5b5, 0x77ac9c65,
0x592b0275, 0x6ea6e483, 0xbd41fbd4, 0x831153b5, 0xee66dfab,
0x2db43210, 0x98fb213f, 0xbeef0ee4, 0x3da88fc2, 0x930aa725,
0xe003826f, 0x0a0e6e70, 0x46d22ffc, 0x5c26c926, 0x5ac42aed,
0x9d95b3df, 0x8baf63de, 0x3c77b2a8, 0x47edaee6, 0x1482353b,
0x4cf10364, 0xbc423001, 0xd0f89791, 0x0654be30, 0xd6ef5218,
0x5565a910, 0x5771202a, 0x32bbd1b8, 0xb8d2d0c8, 0x5141ab53,
0xdf8eeb99, 0xe19b48a8, 0xc5c95a63, 0xe3418acb, 0x7763e373,
0xd6b2b8a3, 0x5defb2fc, 0x43172f60, 0xa1f0ab72, 0x1a6439ec,
0x23631e28, 0xde82bde9, 0xb2c67915, 0xe372532b, 0xea26619c,
0x21c0c207, 0xcde0eb1e, 0xee6ed178, 0x72176fba, 0xa2c898a6,
0xbef90dae, 0x131c471b, 0x23047d84, 0x40c72493, 0x15c9bebc,
0x9c100d4c, 0xcb3e42b6, 0xfc657e2a, 0x3ad6faec, 0x4a475817,
}
--[=[
The 'little sigma 0' function for the SHA-512 block fill operation.
]=]
local function lil_sig0(front: number, back: number): (number, number)
-- (w[i-15] rightrotate 1) xor (w[i-15] rightrotate 8) xor (w[i-15] rightshift 7)
-- rrotate(x, n) = (x >> n) | (x << 32 - n)
-- rshift(x, n) = (x >> n)
-- sig0(x) = rrotate(x, 1) ^ rrotate(x, 8) ^ rshift(x, 7)
-- = (x >> 1 | x << 63) ^ (x >> 8 | x << 24) ^ (x >> 7)
-- since XOR cancels out OR, we can just batch it all:
--stylua: ignore
return bit32.bxor(
bit32.rshift(front, 1), bit32.lshift(back, 31),
bit32.rshift(front, 8), bit32.lshift(back, 24),
bit32.rshift(front, 7)
), bit32.bxor(
bit32.rshift(back, 1), bit32.lshift(front, 31),
bit32.rshift(back, 8), bit32.lshift(front, 24),
bit32.rshift(back, 7), bit32.lshift(front, 25)
)
end
--[=[
The 'little sigma 1' function for the SHA-512 block fill operation.
]=]
local function lil_sig1(front: number, back: number): (number, number)
-- (w[i-2] rightrotate 19) xor (w[i-2] rightrotate 61) xor (w[i-2] rightshift 6)
-- rrotate(x, n) = (x >> n) | (x << 32 - n) if x <= 32
-- rrotate(x, n) = (n >> n % 32) | (n << (32 - n % 32)) if x > 32
-- sig1(x) = rrotate(x, 19) ^ rrotate(x, 61) ^ rshift(x, 6)
-- = (x >> 19 | x << 13) ^ (x >> 29 | x << 3) ^ (x >> 6)
--stylua: ignore
return bit32.bxor(
bit32.rshift(front, 19), bit32.lshift(back, 13),
bit32.lshift(front, 3), bit32.rshift(back, 29), -- Reversed
bit32.rshift(front, 6)
), bit32.bxor(
bit32.rshift(back, 19), bit32.lshift(front, 13),
bit32.lshift(back, 3), bit32.rshift(front, 29), -- Reversed
bit32.rshift(back, 6), bit32.lshift(front, 26)
)
end
--[=[
The 'big sigma 0' function for the SHA-512 compression rounds.
]=]
local function big_sig0(front: number, back: number): (number, number)
--(a rightrotate 28) xor (a rightrotate 34) xor (a rightrotate 39)
-- Sig0(x) = (x >> 28) | x >> 4) ^ (x >> 2 | x << 30) ^ (x >> 7 | x << 25)
--stylua: ignore
return bit32.bxor(
bit32.rshift(front, 28), bit32.lshift(back, 4),
bit32.lshift(front, 30), bit32.rshift(back, 2), -- Reversed
bit32.lshift(front, 25), bit32.rshift(back, 7) -- Reversed
), bit32.bxor(
bit32.rshift(back, 28), bit32.lshift(front, 4),
bit32.lshift(back, 30), bit32.rshift(front, 2), -- Reversed
bit32.lshift(back, 25), bit32.rshift(front, 7) -- Reversed
)
end
--[=[
The 'big sigma 1' function for the SHA-512 compression rounds.
]=]
local function big_sig1(front: number, back: number): (number, number)
--(e rightrotate 14) xor (e rightrotate 18) xor (e rightrotate 41)
-- Sig1(x) = (x >> 14 | x << 18) ^ (x >> 18 | x << 14) ^ (x >> 9 | x << 23)
--stylua: ignore
return bit32.bxor(
bit32.rshift(front, 14), bit32.lshift(back, 18),
bit32.rshift(front, 18), bit32.lshift(back, 14),
bit32.lshift(front, 23), bit32.rshift(back, 9) -- Reversed
), bit32.bxor(
bit32.rshift(back, 14), bit32.lshift(front, 18),
bit32.rshift(back, 18), bit32.lshift(front, 14),
bit32.lshift(back, 23), bit32.rshift(front, 9) -- Reversed
)
end
--[=[
Proceeses `message` as block of 128 bytes from `start` to `finish`.
Expects that `(finish - start) % 128 == 0`.
]=]
local function processBlocks(
digestFront: { number },
digestBack: { number },
message: string,
start: number,
finish: number
)
local block_front = BLOCK_FRONT
local block_back = BLOCK_BACK
local d1_front, d2_front, d3_front, d4_front = digestFront[1], digestFront[2], digestFront[3], digestFront[4]
local d5_front, d6_front, d7_front, d8_front = digestFront[5], digestFront[6], digestFront[7], digestFront[8]
local d1_back, d2_back, d3_back, d4_back = digestBack[1], digestBack[2], digestBack[3], digestBack[4]
local d5_back, d6_back, d7_back, d8_back = digestBack[5], digestBack[6], digestBack[7], digestBack[8]
for i = start, finish, 128 do
for t = 1, 16 do
local a, b, c, d, e, f, g, h = string.byte(message, i, i + 7)
block_front[t] = bit32.bor(bit32.lshift(a, 24), bit32.lshift(b, 16), bit32.lshift(c, 8), d)
block_back[t] = bit32.bor(bit32.lshift(e, 24), bit32.lshift(f, 16), bit32.lshift(g, 8), h)
i += 8
end
for t = 17, 80 do
local s0_front, s0_back = lil_sig0(block_front[t - 15], block_back[t - 15])
local s1_front, s1_back = lil_sig1(block_front[t - 2], block_back[t - 2])
local temp = block_back[t - 16] + s0_back + block_back[t - 7] + s1_back
-- We are meant to be performing addition here, but are not
-- properly carrying bits. So, we have to simulate that.
-- This is the equivalent to temp & 0xFFFF_FFFF, which
-- truncates the integer for us.
block_back[t] = bit32.bor(temp, 0)
block_front[t] = block_front[t - 16]
+ s0_front
+ block_front[t - 7]
+ s1_front
-- After that, any leftover bits are shifted down until
-- they can be added safely
+ temp // 2 ^ 32
-- block_front[t] %= 2 ^ 32 -- unnecessary but good for debugging
end
local a_front, b_front, c_front, d_front = d1_front, d2_front, d3_front, d4_front
local e_front, f_front, g_front, h_front = d5_front, d6_front, d7_front, d8_front
local a_back, b_back, c_back, d_back = d1_back, d2_back, d3_back, d4_back
local e_back, f_back, g_back, h_back = d5_back, d6_back, d7_back, d8_back
for t = 1, 80 do
--Ch(e, f, g) = bit32.band(e, f) + bit32.band(-1 - e, g)
--Maj(a, b, c) = bit32.band(c, b) + bit32.band(a, bit32.bxor(c, b))
local s0_front, s0_back = big_sig0(a_front, a_back)
local s1_front, s1_back = big_sig1(e_front, e_back)
--stylua: ignore start
-- h + S1 + Ch(e, f, g) + K[t] + W[t]
local temp1_back = h_back + s1_back + bit32.bor(bit32.band(e_back, f_back), bit32.band(-1 - e_back, g_back), 0) + K_BACK[t] + block_back[t]
local temp1_front = h_front + s1_front + bit32.bor(bit32.band(e_front, f_front), bit32.band(-1 - e_front, g_front), 0) + K_FRONT[t] + block_front[t] + temp1_back // 2 ^ 32
temp1_back = bit32.bor(temp1_back, 0)
-- S0 + Maj
local temp2_back = s0_back + bit32.band(c_back, b_back) + bit32.band(a_back, bit32.bxor(c_back, b_back))
local temp2_front = s0_front + bit32.band(c_front, b_front) + bit32.band(a_front, bit32.bxor(c_front, b_front))
--stylua: ignore end
h_front, h_back = g_front, g_back
g_front, g_back = f_front, f_back
f_front, f_back = e_front, e_back
e_back = temp1_back + d_back
e_front = temp1_front + d_front + e_back // 2 ^ 32
e_back = bit32.bor(e_back, 0)
d_front, d_back = c_front, c_back
c_front, c_back = b_front, b_back
b_front, b_back = a_front, a_back
a_back = temp1_back + temp2_back
a_front = temp1_front + temp2_front + a_back // 2 ^ 32
a_back = bit32.bor(a_back, 0)
end
d1_back = d1_back + a_back
d1_front = bit32.bor(d1_front + a_front + d1_back // 2 ^ 32, 0)
d1_back = bit32.bor(d1_back, 0)
d2_back = d2_back + b_back
d2_front = bit32.bor(d2_front + b_front + d2_back // 2 ^ 32, 0)
d2_back = bit32.bor(d2_back, 0)
d3_back = d3_back + c_back
d3_front = bit32.bor(d3_front + c_front + d3_back // 2 ^ 32, 0)
d3_back = bit32.bor(d3_back, 0)
d4_back = d4_back + d_back
d4_front = bit32.bor(d4_front + d_front + d4_back // 2 ^ 32, 0)
d4_back = bit32.bor(d4_back, 0)
d5_back = d5_back + e_back
d5_front = bit32.bor(d5_front + e_front + d5_back // 2 ^ 32, 0)
d5_back = bit32.bor(d5_back, 0)
d6_back = d6_back + f_back
d6_front = bit32.bor(d6_front + f_front + d6_back // 2 ^ 32, 0)
d6_back = bit32.bor(d6_back, 0)
d7_back = d7_back + g_back
d7_front = bit32.bor(d7_front + g_front + d7_back // 2 ^ 32, 0)
d7_back = bit32.bor(d7_back, 0)
d8_back = d8_back + h_back
d8_front = bit32.bor(d8_front + h_front + d8_back // 2 ^ 32, 0)
d8_back = bit32.bor(d8_back, 0)
end
digestFront[1], digestBack[1] = d1_front, d1_back
digestFront[2], digestBack[2] = d2_front, d2_back
digestFront[3], digestBack[3] = d3_front, d3_back
digestFront[4], digestBack[4] = d4_front, d4_back
digestFront[5], digestBack[5] = d5_front, d5_back
digestFront[6], digestBack[6] = d6_front, d6_back
digestFront[7], digestBack[7] = d7_front, d7_back
digestFront[8], digestBack[8] = d8_front, d8_back
end
--[=[
Computes the SHA-512 hash for `message` and returns it.
This function returns both a hexadecimal encoded string of the hash
and returns a **read-only** table containing sixteen 32-bit integers
that make up the hash.
This functions will raise an error if the message passed is over `2^50` bytes
in length. This is due to a Luau limitation.
@param message -- The payload to compute the SHA-512 hash of
@return string -- The computed SHA-512 hash as a string of hexadecimal digits
@return {number} -- The computed SHA-512 hash as an array of 16 integers
]=]
local function sha512(message: string): string
local digest_front =
{ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 }
local digest_back =
{ 0xf3bcc908, 0x84caa73b, 0xfe94f82b, 0x5f1d36f1, 0xade682d1, 0x2b3e6c1f, 0xfb41bd6b, 0x137e2179 }
local messageLen = #message
-- The max exactly representable value of a double is 2^53
-- We need to multiply the length of a number by 8 though
-- So, 2^53 / 8 = 2 ^ 50 = max supported size
-- In practice, this is almost 26 terabytes of data
-- Which is technically possible but why would you pass that through
-- a Luau function?
if messageLen > 2 ^ 50 then
error("cannot calculate the SHA-512 hash of a string longer than 2^50 bytes", 2)
end
-- SHA-512 has a block size of 1024 bits or 128 bytes
local leftover = messageLen % 128
if messageLen >= 128 then
processBlocks(digest_front, digest_back, message, 1, messageLen - leftover)
end
-- Raise `leftover` to next multiple of 128 so that we can calculate
-- how much padding we need without a branch or loop.
-- The number here is just masking off the last 6 bits.
local nextMultipleOf128 = bit32.band(leftover + 64, 0xfffff000)
local finalMessage = {
if leftover ~= 0 then string.sub(message, -leftover) else "",
"\x80",
string.rep("\0", (nextMultipleOf128 - leftover - 17) % 128),
string.pack(">L", messageLen * 8 / 2 ^ 32),
string.pack(">L", messageLen * 8 % 2 ^ 32),
}
local finalBlock = table.concat(finalMessage)
processBlocks(digest_front, digest_back, finalBlock, 1, #finalBlock)
local merged = table.create(16)
for i = 1, 8 do
merged[2 * i - 1] = digest_front[i]
merged[2 * i] = digest_back[i]
end
--stylua: ignore
return string.format(
"%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x",
digest_front[1], digest_back[1],
digest_front[2], digest_back[2],
digest_front[3], digest_back[3],
digest_front[4], digest_back[4],
digest_front[5], digest_back[5],
digest_front[6], digest_back[6],
digest_front[7], digest_back[7],
digest_front[8], digest_back[8]
), table.freeze(merged)
end
return sha512
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment