local P=bit32.band local T=bit32.bor local k=bit32.bxor local z=bit32.lrotate local Q=k local V=k local X=k local x=string.byte local Y=string.char local g=string.format local N=string.rep function uint32_ternary(T,z,Q)return k(Q,P(T,k(z,Q)))end function uint32_majority(k,z,Q)return T(P(k,T(z,Q)),P(z,Q))end local function b(P)return string.gsub(P,"\046\046",function(P)return string.char(tonumber(P,16))end)end function bytes_to_uint32(P,T,k,z)return((P*16777216+T*65536)+k*256)+z end function uint32_to_bytes(P)local T=P%256 P=(P-T)/256 local k=P%256 P=(P-k)/256 local z=P%256 local Q=(P-z)/256 return Q,z,k,T end local h={}function h.sha1(P)local T=Y(128)local k=(#P+1)+8 local Q=N(Y(0),-k%64)local b=Y(0,0,0,0,uint32_to_bytes(#P*8))P=P..(T..(Q..b))assert(#P%64==0,"")local h=1732584193 local E=4023233417 local f=2562383102 local e=271733878 local j=3285377520 local L={}for T=1,#P,64 do local k=T for T=0,15,1 do L[T]=bytes_to_uint32(x(P,k,k+3))k=k+4 end for P=16,79,1 do L[P]=z(X(L[P-3],L[P-8],L[P-14],L[P-16]),1)end local Q=h local Y=E local g=f local N=e local b=j for P=0,79,1 do local T local k if P<=19 then T=uint32_ternary(Y,g,N)k=1518500249 elseif P<=39 then T=V(Y,g,N)k=1859775393 elseif P<=59 then T=uint32_majority(Y,g,N)k=2400959708 else T=V(Y,g,N)k=3395469782 end local X=((((z(Q,5)+T)+b)+k)+L[P])%4294967296 b=N N=g g=z(Y,30)Y=Q Q=X end h=(h+Q)%4294967296 E=(E+Y)%4294967296 f=(f+g)%4294967296 e=(e+N)%4294967296 j=(j+b)%4294967296 end return g("\037\048\056\120\037\048\056\120\037\048\056\120\037\048\056\120\037\048\056\120",h,E,f,e,j)end function h.binary(P)return b(h.sha1(P))end local E={}local f={}for P=0,255,1 do E[Y(P)]=Y(Q(92,P))f[Y(P)]=Y(Q(54,P))end local e=64 function h.hmac(P,T)if#P>e then P=h.binary(P)end local k=P:gsub("\046",f)..N(Y(54),e-#P)local z=P:gsub("\046",E)..N(Y(92),e-#P)return h.sha1(z..h.binary(k..T))end function h.hmac_binary(P,T)return b(h.hmac(P,T))end setmetatable(h,{__call=function(P,T)return h.sha1(T)end})return h
local band = bit32.band;
local bor = bit32.bor;
local bxor = bit32.bxor;
local uint32_lrot = bit32.lrotate;

local byte_xor = bxor;
local uint32_xor_3 = bxor;
local uint32_xor_4 = bxor;

local sbyte = string.byte;
local schar = string.char;
local sformat = string.format;
local srep = string.rep;

function uint32_ternary(a, b, c)
    -- c ~ (a & (b ~ c)) has less bitwise operations than (a & b) | (~a & c).
    return bxor(c, band(a, bxor(b, c)));
end

function uint32_majority(a, b, c)
    -- (a & (b | c)) | (b & c) has less bitwise operations than (a & b) | (a & c) | (b & c).
    return bor(band(a, bor(b, c)), band(b, c));
end


local function hex_to_binary(hex)
	return (string.gsub(hex, "..", function(hexval)
		return string.char(tonumber(hexval, 16));
	end));
end

-- Merges four bytes into a uint32 number.
function bytes_to_uint32(a, b, c, d)
    return (a * 0x1000000 + b * 0x10000 + c * 0x100 + d);
end

-- Splits a uint32 number into four bytes.
function uint32_to_bytes(a)
    local a4 = a % 256;
    a = (a - a4) / 256;
    local a3 = a % 256;
    a = (a - a3) / 256;
    local a2 = a % 256;
    local a1 = (a - a2) / 256;
    return a1, a2, a3, a4;
end

local sha1 = {};

-- Calculates SHA1 for a string, returns it encoded as 40 hexadecimal digits.
function sha1.sha1(str)
	-- Input preprocessing.
	-- First, append a `1` bit and seven `0` bits.
	local first_append = schar(0x80);

	-- Next, append some zero bytes to make the length of the final message a multiple of 64.
	-- Eight more bytes will be added next.
	local non_zero_message_bytes = #str + 1 + 8;
	local second_append = srep(schar(0), -non_zero_message_bytes % 64);

	-- Finally, append the length of the original message in bits as a 64-bit number.
	-- Assume that it fits into the lower 32 bits.
	local third_append = schar(0, 0, 0, 0, uint32_to_bytes(#str * 8));

	str = str .. first_append .. second_append .. third_append;
	assert(#str % 64 == 0, "");

	-- Initialize hash value.
	local h0 = 0x67452301;
	local h1 = 0xEFCDAB89;
	local h2 = 0x98BADCFE;
	local h3 = 0x10325476;
	local h4 = 0xC3D2E1F0;

	local w = {};

	-- Process the input in successive 64-byte chunks.
	for chunk_start = 1, #str, 64 do
		-- Load the chunk into W[0..15] as uint32 numbers.
		local uint32_start = chunk_start;

		for i = 0, 15 do
			w[i] = bytes_to_uint32(sbyte(str, uint32_start, uint32_start + 3));
			uint32_start = uint32_start + 4;
		end;

		-- Extend the input vector.
		for i = 16, 79 do
			w[i] = uint32_lrot(uint32_xor_4(w[i - 3], w[i - 8], w[i - 14], w[i - 16]), 1);
		end;

		-- Initialize hash value for this chunk.
		local a = h0;
		local b = h1;
		local c = h2;
		local d = h3;
		local e = h4;

		-- Main loop.
		for i = 0, 79 do
			local f;
			local k;

			if (i <= 19) then
				f = uint32_ternary(b, c, d);
				k = 0x5A827999;
			elseif (i <= 39) then
				f = uint32_xor_3(b, c, d);
				k = 0x6ED9EBA1;
			elseif (i <= 59) then
				f = uint32_majority(b, c, d);
				k = 0x8F1BBCDC;
			else
				f = uint32_xor_3(b, c, d);
				k = 0xCA62C1D6;
			end;

			local temp = (uint32_lrot(a, 5) + f + e + k + w[i]) % 4294967296;
			e = d;
			d = c;
			c = uint32_lrot(b, 30);
			b = a;
			a = temp;
		end;

		-- Add this chunk's hash to result so far.
		h0 = (h0 + a) % 4294967296;
		h1 = (h1 + b) % 4294967296;
		h2 = (h2 + c) % 4294967296;
		h3 = (h3 + d) % 4294967296;
		h4 = (h4 + e) % 4294967296;
	end;

	return sformat("%08x%08x%08x%08x%08x", h0, h1, h2, h3, h4);
end

function sha1.binary(str)
	return hex_to_binary(sha1.sha1(str));
end

-- Precalculate replacement tables.
local xor_with_0x5c = {};
local xor_with_0x36 = {};

for i = 0, 0xff do
	xor_with_0x5c[schar(i)] = schar(byte_xor(0x5c, i));
	xor_with_0x36[schar(i)] = schar(byte_xor(0x36, i));
end;

-- 512 bits.
local BLOCK_SIZE = 64;

function sha1.hmac(key, text)
	if (#key > BLOCK_SIZE) then
		key = sha1.binary(key);
	end;

	local key_xord_with_0x36 = key:gsub('.', xor_with_0x36) .. srep(schar(0x36), BLOCK_SIZE - #key);
	local key_xord_with_0x5c = key:gsub('.', xor_with_0x5c) .. srep(schar(0x5c), BLOCK_SIZE - #key);
	return sha1.sha1(key_xord_with_0x5c .. sha1.binary(key_xord_with_0x36 .. text));
end

function sha1.hmac_binary(key, text)
	return hex_to_binary(sha1.hmac(key, text));
end

setmetatable(
	sha1,
	{
		__call = function(_, str)
			return sha1.sha1(str);
		end
	}
)

return sha1;
