--#selene: allow(unused_variable)

export type MetadataKind = "file" | "dir" | "symlink"

--[=[
	@interface MetadataPermissions
	@within FS

	Permissions for the given file or directory.

	This is a dictionary that will contain the following values:

	* `readOnly` - If the target path is read-only or not
]=]
export type MetadataPermissions = {
	readOnly: boolean,
}

-- FIXME: We lose doc comments here below in Metadata because of the union type

--[=[
	@interface Metadata
	@within FS

	Metadata for the given file or directory.

	This is a dictionary that will contain the following values:

	* `kind` - If the target path is a `file`, `dir` or `symlink`
	* `exists` - If the target path exists
	* `createdAt` - The timestamp at which the file or directory was created
	* `modifiedAt` - The timestamp at which the file or directory was last modified
	* `accessedAt` - The timestamp at which the file or directory was last accessed
	* `permissions` - Current permissions for the file or directory

	Note that timestamps are relative to the unix epoch, and
	may not be accurate if the system clock is not accurate.
]=]
export type Metadata = {
	kind: MetadataKind,
	exists: true,
	createdAt: number,
	modifiedAt: number,
	accessedAt: number,
	permissions: MetadataPermissions,
} | {
	kind: nil,
	exists: false,
	createdAt: nil,
	modifiedAt: nil,
	accessedAt: nil,
	permissions: nil,
}

--[=[
	@interface WriteOptions
	@within FS

	Options for filesystem APIs what write to files and/or directories.

	This is a dictionary that may contain one or more of the following values:

	* `overwrite` - If the target path should be overwritten or not, in the case that it already exists
]=]
export type WriteOptions = {
	overwrite: boolean?,
}

--[=[
	@class FS

	Built-in library for filesystem access

	### Example usage

	```lua
	local fs = require("@lune/fs")

	-- Reading a file
	local myTextFile: string = fs.readFile("myFileName.txt")

	-- Reading entries (files & dirs) in a directory
	for _, entryName in fs.readDir("myDirName") do
		if fs.isFile("myDirName/" .. entryName) then
			print("Found file " .. entryName)
		elseif fs.isDir("myDirName/" .. entryName) then
			print("Found subdirectory " .. entryName)
		end
	end
	```
]=]
local fs = {}

--[=[
	@within FS
	@tag must_use

	Reads a file at `path`.

	An error will be thrown in the following situations:

	* `path` does not point to an existing file.
	* The current process lacks permissions to read the file.
	* Some other I/O error occurred.

	@param path The path to the file to read
	@return The contents of the file
]=]
function fs.readFile(path: string): string
	return nil :: any
end

--[=[
	@within FS
	@tag must_use

	Reads entries in a directory at `path`.

	An error will be thrown in the following situations:

	* `path` does not point to an existing directory.
	* The current process lacks permissions to read the contents of the directory.
	* Some other I/O error occurred.

	@param path The directory path to search in
	@return A list of files & directories found
]=]
function fs.readDir(path: string): { string }
	return {}
end

--[=[
	@within FS

	Writes to a file at `path`.

	An error will be thrown in the following situations:

	* The file's parent directory does not exist.
	* The current process lacks permissions to write to the file.
	* Some other I/O error occurred.

	@param path The path of the file
	@param contents The contents of the file
]=]
function fs.writeFile(path: string, contents: string) end

--[=[
	@within FS

	Creates a directory and its parent directories if they are missing.

	An error will be thrown in the following situations:

	* `path` already points to an existing file or directory.
	* The current process lacks permissions to create the directory or its missing parents.
	* Some other I/O error occurred.

	@param path The directory to create
]=]
function fs.writeDir(path: string) end

--[=[
	@within FS

	Removes a file.

	An error will be thrown in the following situations:

	* `path` does not point to an existing file.
	* The current process lacks permissions to remove the file.
	* Some other I/O error occurred.

	@param path The file to remove
]=]
function fs.removeFile(path: string) end

--[=[
	@within FS

	Removes a directory and all of its contents.

	An error will be thrown in the following situations:

	* `path` is not an existing and empty directory.
	* The current process lacks permissions to remove the directory.
	* Some other I/O error occurred.

	@param path The directory to remove
]=]
function fs.removeDir(path: string) end

--[=[
	@within FS
	@tag must_use

	Gets metadata for the given path.

	An error will be thrown in the following situations:

	* The current process lacks permissions to read at `path`.
	* Some other I/O error occurred.

	@param path The path to get metadata for
	@return Metadata for the path
]=]
function fs.metadata(path: string): Metadata
	return nil :: any
end

--[=[
	@within FS
	@tag must_use

	Checks if a given path is a file.

	An error will be thrown in the following situations:

	* The current process lacks permissions to read at `path`.
	* Some other I/O error occurred.

	@param path The file path to check
	@return If the path is a file or not
]=]
function fs.isFile(path: string): boolean
	return nil :: any
end

--[=[
	@within FS
	@tag must_use

	Checks if a given path is a directory.

	An error will be thrown in the following situations:

	* The current process lacks permissions to read at `path`.
	* Some other I/O error occurred.

	@param path The directory path to check
	@return If the path is a directory or not
]=]
function fs.isDir(path: string): boolean
	return nil :: any
end

--[=[
	@within FS

	Moves a file or directory to a new path.

	Throws an error if a file or directory already exists at the target path.
	This can be bypassed by passing `true` as the third argument, or a dictionary of options.
	Refer to the documentation for `WriteOptions` for specific option keys and their values.

	An error will be thrown in the following situations:

	* The current process lacks permissions to read at `from` or write at `to`.
	* The new path exists on a different mount point.
	* Some other I/O error occurred.

	@param from The path to move from
	@param to The path to move to
	@param overwriteOrOptions Options for the target path, such as if should be overwritten if it already exists
]=]
function fs.move(from: string, to: string, overwriteOrOptions: (boolean | WriteOptions)?) end

--[=[
	@within FS

	Copies a file or directory recursively to a new path.

	Throws an error if a file or directory already exists at the target path.
	This can be bypassed by passing `true` as the third argument, or a dictionary of options.
	Refer to the documentation for `WriteOptions` for specific option keys and their values.

	An error will be thrown in the following situations:

	* The current process lacks permissions to read at `from` or write at `to`.
	* Some other I/O error occurred.

	@param from The path to copy from
	@param to The path to copy to
	@param overwriteOrOptions Options for the target path, such as if should be overwritten if it already exists
]=]
function fs.copy(from: string, to: string, overwriteOrOptions: (boolean | WriteOptions)?) end

return fs


--!strict
--#selene: allow(incorrect_standard_library_use)

-- This is kind of a nightmare, but it gives intellisense without
-- having to disable type checking.
type Fs = typeof(require(".lune/.typedefs/fs"))
type Process = typeof(require(".lune/.typedefs/process"))

local fs = (require :: any)("@lune/fs") :: Fs
local process = (require :: any)("@lune/process") :: Process

-- TODO: Change the format of test outputs so that this isn't necessary
local function testInfo(msg: string): (string, string, string)
	local memory = string.match(msg, "^Change in memory:%s*(%d+)")
	local name, elapsed = string.match(msg, "([%w_%-]+) tests completed. Took%s*(%S+)%s*$")
	if not (memory and name and elapsed) then
		error("could not parse test output result!", 2)
	end
	return name, elapsed, memory
end

local tests = fs.readDir("tests")
local results = table.create(#tests)

local function runTests(options)
	local maxName, maxElapsed, maxMem = -1, -1, -1

	for i, testFile in fs.readDir("tests") do
		local test = process.spawn("luau", { "tests/" .. testFile, table.unpack(options) })
		if test.ok then
			local name, elapsed, memory = testInfo(test.stdout)
			maxName = math.max(maxName, #name)
			maxElapsed = math.max(maxElapsed, #elapsed)
			maxMem = math.max(maxMem, #memory)
			results[i] = { name, elapsed, memory }
		else
			error(`Test '{testFile}' failed!`, 2)
		end
	end
	local fmt = `Test %-{maxName}s  took %-{maxElapsed}s  [%-{maxMem}s byte change in memory]`
	for _, result in results do
		print(string.format(fmt, result[1], result[2], result[3]))
	end
end

print("Running standard tests...")
runTests({ "-O2" })
print()
print("Running codegen tests...")
runTests({ "-O2", "--codegen" })


--!strict
--!native
--!optimize 2

--- The front half of the initial values used for the hasher state.
local IV_FRONT = { 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 }
--- The back half of the initial values used for the hasher state.
local IV_BACK = { 0xf3bcc908, 0x84caa73b, 0xfe94f82b, 0x5f1d36f1, 0xade682d1, 0x2b3e6c1f, 0xfb41bd6b, 0x137e2179 }

--- A list of offsets to use for each round in the compression algorithm.
local SIGMA = {
	{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 },
	{ 15, 11, 5, 9, 10, 16, 14, 7, 2, 13, 1, 3, 12, 8, 6, 4 },
	{ 12, 9, 13, 1, 6, 3, 16, 14, 11, 15, 4, 7, 8, 2, 10, 5 },
	{ 8, 10, 4, 2, 14, 13, 12, 15, 3, 7, 6, 11, 5, 1, 16, 9 },
	{ 10, 1, 6, 8, 3, 5, 11, 16, 15, 2, 12, 13, 7, 9, 4, 14 },
	{ 3, 13, 7, 11, 1, 12, 9, 4, 5, 14, 8, 6, 16, 15, 2, 10 },
	{ 13, 6, 2, 16, 15, 14, 5, 11, 1, 8, 7, 4, 10, 3, 9, 12 },
	{ 14, 12, 8, 15, 13, 2, 4, 10, 6, 1, 16, 5, 9, 7, 3, 11 },
	{ 7, 16, 15, 10, 12, 4, 1, 9, 13, 3, 14, 8, 2, 5, 11, 6 },
	{ 11, 3, 9, 5, 8, 7, 2, 6, 16, 12, 10, 15, 4, 13, 14, 1 },
	-- There are two additional rounds of compression in blake2b
	{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 },
	{ 15, 11, 5, 9, 10, 16, 14, 7, 2, 13, 1, 3, 12, 8, 6, 4 },
}

--[=[
	The BLAKE2b compression function.

	@param h_front The front half of the internal digest for the algorithm
	@param h_back The back half of the internal digest for the algorithm
	@param m A block of bytes utilized for rounds
	@param t A counter for how many bytes fed into the hasher so far, including this set
	@param f Whether or not this call is the final one
]=]
local function F(
	h_front: { number },
	h_back: { number },
	m_front: { number },
	m_back: { number },
	t: number,
	f: boolean
)
	--stylua: ignore start
	local v_front_1, v_front_2, v_front_3, v_front_4, v_front_5, v_front_6, v_front_7, v_front_8, v_front_9, v_front_10, v_front_11, v_front_12, v_front_13, v_front_14, v_front_15, v_front_16 =
		h_front[1], h_front[2], h_front[3], h_front[4], h_front[5], h_front[6], h_front[7], h_front[8],
		IV_FRONT[1], IV_FRONT[2], IV_FRONT[3], IV_FRONT[4], IV_FRONT[5], IV_FRONT[6], IV_FRONT[7], IV_FRONT[8]
	local v_back_1, v_back_2, v_back_3, v_back_4, v_back_5, v_back_6, v_back_7, v_back_8, v_back_9, v_back_10, v_back_11, v_back_12, v_back_13, v_back_14, v_back_15, v_back_16 =
		h_back[1], h_back[2], h_back[3], h_back[4], h_back[5], h_back[6], h_back[7], h_back[8],
		IV_BACK[1], IV_BACK[2], IV_BACK[3], IV_BACK[4], IV_BACK[5], IV_BACK[6], IV_BACK[7], IV_BACK[8]
	--stylua: ignore end

	v_front_13 = bit32.bxor(v_front_13, t // 2 ^ 32)
	v_back_13 = bit32.bxor(v_back_13, t)
	-- We are never going to handle inputs of greater than 2^50.
	-- So we don't *need* to handle the upper half of the length
	-- It would be `v[14] = v[14] XOR t_upper` though.
	if f then
		v_front_15 = bit32.bnot(v_front_15)
		v_back_15 = bit32.bnot(v_back_15)
	end

	-- Luau's cost function is, unfortunately, rather conservative.
	-- It's a 20-60% uplift in performance to inline these functions so...
	-- I'm sorry that you have to look at this.
	-- I've left them in `do` blocks so that it's easy to hide but here's
	-- 300 lines of mixing.
	local temp = 0
	local x_front, x_back = 0, 0
	local y_front, y_back = 0, 0
	--stylua: ignore
	for _, s in SIGMA do
		-- v_front_1, v_back_1, v_front_5, v_back_5, v_front_9, v_back_9, v_front_13, v_back_13 = G(v_front_1, v_back_1, v_front_5, v_back_5, v_front_9, v_back_9, v_front_13, v_back_13, m_front[s[1]], m_back[s[1]], m_front[s[2]], m_back[s[2]])
		do
			x_front, x_back = m_front[s[1]], m_back[s[1]]
			y_front, y_back = m_front[s[2]], m_back[s[2]]

			temp = v_back_1 + v_back_5 + x_back
			v_front_1 = v_front_1 + v_front_5 + x_front + temp // 2 ^ 32
			v_back_1 = temp % 2 ^ 32

			temp = v_front_13
			v_front_13 = bit32.bxor(v_back_13, v_back_1)
			v_back_13 = bit32.bxor(temp, v_front_1)

			temp = v_back_9 + v_back_13
			v_front_9 = v_front_9 + v_front_13 + temp // 2 ^ 32
			v_back_9 = temp % 2 ^ 32

			temp = v_front_5
			v_front_5 = bit32.bxor(bit32.rshift(v_front_5, 24), bit32.lshift(v_back_5, 8), bit32.rshift(v_front_9, 24), bit32.lshift(v_back_9, 8))
			v_back_5 = bit32.bxor(bit32.rshift(v_back_5, 24), bit32.lshift(temp, 8), bit32.rshift(v_back_9, 24), bit32.lshift(v_front_9, 8))

			temp = v_back_1 + v_back_5 + y_back
			v_front_1 = v_front_1 + v_front_5 + y_front + temp // 2 ^ 32
			v_back_1 = temp % 2 ^ 32

			temp = v_front_13
			v_front_13 = bit32.bxor(bit32.rshift(v_front_13, 16), bit32.lshift(v_back_13, 16), bit32.rshift(v_front_1, 16), bit32.lshift(v_back_1, 16))
			v_back_13 = bit32.bxor(bit32.rshift(v_back_13, 16), bit32.lshift(temp, 16), bit32.rshift(v_back_1, 16), bit32.lshift(v_front_1, 16))

			temp = v_back_9 + v_back_13
			v_front_9 = v_front_9 + v_front_13 + temp // 2 ^ 32
			v_back_9 = temp % 2 ^ 32

			temp = v_front_5
			v_front_5 = bit32.bxor(bit32.lshift(v_front_5, 1), bit32.rshift(v_back_5, 31), bit32.lshift(v_front_9, 1), bit32.rshift(v_back_9, 31))
			v_back_5 = bit32.bxor(bit32.lshift(v_back_5, 1), bit32.rshift(temp, 31), bit32.lshift(v_back_9, 1), bit32.rshift(v_front_9, 31))
		end

		-- v_front_2, v_back_2, v_front_6, v_back_6, v_front_10, v_back_10, v_front_14, v_back_14 = G(v_front_2, v_back_2, v_front_6, v_back_6, v_front_10, v_back_10, v_front_14, v_back_14, m_front[s[3]], m_back[s[3]], m_front[s[4]], m_back[s[4]])
		do
			x_front, x_back = m_front[s[3]], m_back[s[3]]
			y_front, y_back = m_front[s[4]], m_back[s[4]]

			temp = v_back_2 + v_back_6 + x_back
			v_front_2 = v_front_2 + v_front_6 + x_front + temp // 2 ^ 32
			v_back_2 = temp % 2 ^ 32

			temp = v_front_14
			v_front_14 = bit32.bxor(v_back_14, v_back_2)
			v_back_14 = bit32.bxor(temp, v_front_2)

			temp = v_back_10 + v_back_14
			v_front_10 = v_front_10 + v_front_14 + temp // 2 ^ 32
			v_back_10 = temp % 2 ^ 32

			temp = v_front_6
			v_front_6 = bit32.bxor(bit32.rshift(v_front_6, 24), bit32.lshift(v_back_6, 8), bit32.rshift(v_front_10, 24), bit32.lshift(v_back_10, 8))
			v_back_6 = bit32.bxor(bit32.rshift(v_back_6, 24), bit32.lshift(temp, 8), bit32.rshift(v_back_10, 24), bit32.lshift(v_front_10, 8))

			temp = v_back_2 + v_back_6 + y_back
			v_front_2 = v_front_2 + v_front_6 + y_front + temp // 2 ^ 32
			v_back_2 = temp % 2 ^ 32

			temp = v_front_14
			v_front_14 = bit32.bxor(bit32.rshift(v_front_14, 16), bit32.lshift(v_back_14, 16), bit32.rshift(v_front_2, 16), bit32.lshift(v_back_2, 16))
			v_back_14 = bit32.bxor(bit32.rshift(v_back_14, 16), bit32.lshift(temp, 16), bit32.rshift(v_back_2, 16), bit32.lshift(v_front_2, 16))

			temp = v_back_10 + v_back_14
			v_front_10 = v_front_10 + v_front_14 + temp // 2 ^ 32
			v_back_10 = temp % 2 ^ 32

			temp = v_front_6
			v_front_6 = bit32.bxor(bit32.lshift(v_front_6, 1), bit32.rshift(v_back_6, 31), bit32.lshift(v_front_10, 1), bit32.rshift(v_back_10, 31))
			v_back_6 = bit32.bxor(bit32.lshift(v_back_6, 1), bit32.rshift(temp, 31), bit32.lshift(v_back_10, 1), bit32.rshift(v_front_10, 31))
		end

		-- v_front_3, v_back_3, v_front_7, v_back_7, v_front_11, v_back_11, v_front_15, v_back_15 = G(v_front_3, v_back_3, v_front_7, v_back_7, v_front_11, v_back_11, v_front_15, v_back_15, m_front[s[5]], m_back[s[5]], m_front[s[6]], m_back[s[6]])
		do
			x_front, x_back = m_front[s[5]], m_back[s[5]]
			y_front, y_back = m_front[s[6]], m_back[s[6]]

			temp = v_back_3 + v_back_7 + x_back
			v_front_3 = v_front_3 + v_front_7 + x_front + temp // 2 ^ 32
			v_back_3 = temp % 2 ^ 32

			temp = v_front_15
			v_front_15 = bit32.bxor(v_back_15, v_back_3)
			v_back_15 = bit32.bxor(temp, v_front_3)

			temp = v_back_11 + v_back_15
			v_front_11 = v_front_11 + v_front_15 + temp // 2 ^ 32
			v_back_11 = temp % 2 ^ 32

			temp = v_front_7
			v_front_7 = bit32.bxor(bit32.rshift(v_front_7, 24), bit32.lshift(v_back_7, 8), bit32.rshift(v_front_11, 24), bit32.lshift(v_back_11, 8))
			v_back_7 = bit32.bxor(bit32.rshift(v_back_7, 24), bit32.lshift(temp, 8), bit32.rshift(v_back_11, 24), bit32.lshift(v_front_11, 8))

			temp = v_back_3 + v_back_7 + y_back
			v_front_3 = v_front_3 + v_front_7 + y_front + temp // 2 ^ 32
			v_back_3 = temp % 2 ^ 32

			temp = v_front_15
			v_front_15 = bit32.bxor(bit32.rshift(v_front_15, 16), bit32.lshift(v_back_15, 16), bit32.rshift(v_front_3, 16), bit32.lshift(v_back_3, 16))
			v_back_15 = bit32.bxor(bit32.rshift(v_back_15, 16), bit32.lshift(temp, 16), bit32.rshift(v_back_3, 16), bit32.lshift(v_front_3, 16))

			temp = v_back_11 + v_back_15
			v_front_11 = v_front_11 + v_front_15 + temp // 2 ^ 32
			v_back_11 = temp % 2 ^ 32

			temp = v_front_7
			v_front_7 = bit32.bxor(bit32.lshift(v_front_7, 1), bit32.rshift(v_back_7, 31), bit32.lshift(v_front_11, 1), bit32.rshift(v_back_11, 31))
			v_back_7 = bit32.bxor(bit32.lshift(v_back_7, 1), bit32.rshift(temp, 31), bit32.lshift(v_back_11, 1), bit32.rshift(v_front_11, 31))
		end

		-- v_front_4, v_back_4, v_front_8, v_back_8, v_front_12, v_back_12, v_front_16, v_back_16 = G(v_front_4, v_back_4, v_front_8, v_back_8, v_front_12, v_back_12, v_front_16, v_back_16, m_front[s[7]], m_back[s[7]], m_front[s[8]], m_back[s[8]])
		do
			x_front, x_back = m_front[s[7]], m_back[s[7]]
			y_front, y_back = m_front[s[8]], m_back[s[8]]

			temp = v_back_4 + v_back_8 + x_back
			v_front_4 = v_front_4 + v_front_8 + x_front + temp // 2 ^ 32
			v_back_4 = temp % 2 ^ 32

			temp = v_front_16
			v_front_16 = bit32.bxor(v_back_16, v_back_4)
			v_back_16 = bit32.bxor(temp, v_front_4)

			temp = v_back_12 + v_back_16
			v_front_12 = v_front_12 + v_front_16 + temp // 2 ^ 32
			v_back_12 = temp % 2 ^ 32

			temp = v_front_8
			v_front_8 = bit32.bxor(bit32.rshift(v_front_8, 24), bit32.lshift(v_back_8, 8), bit32.rshift(v_front_12, 24), bit32.lshift(v_back_12, 8))
			v_back_8 = bit32.bxor(bit32.rshift(v_back_8, 24), bit32.lshift(temp, 8), bit32.rshift(v_back_12, 24), bit32.lshift(v_front_12, 8))

			temp = v_back_4 + v_back_8 + y_back
			v_front_4 = v_front_4 + v_front_8 + y_front + temp // 2 ^ 32
			v_back_4 = temp % 2 ^ 32

			temp = v_front_16
			v_front_16 = bit32.bxor(bit32.rshift(v_front_16, 16), bit32.lshift(v_back_16, 16), bit32.rshift(v_front_4, 16), bit32.lshift(v_back_4, 16))
			v_back_16 = bit32.bxor(bit32.rshift(v_back_16, 16), bit32.lshift(temp, 16), bit32.rshift(v_back_4, 16), bit32.lshift(v_front_4, 16))

			temp = v_back_12 + v_back_16
			v_front_12 = v_front_12 + v_front_16 + temp // 2 ^ 32
			v_back_12 = temp % 2 ^ 32

			temp = v_front_8
			v_front_8 = bit32.bxor(bit32.lshift(v_front_8, 1), bit32.rshift(v_back_8, 31), bit32.lshift(v_front_12, 1), bit32.rshift(v_back_12, 31))
			v_back_8 = bit32.bxor(bit32.lshift(v_back_8, 1), bit32.rshift(temp, 31), bit32.lshift(v_back_12, 1), bit32.rshift(v_front_12, 31))
		end

		-- v_front_1, v_back_1, v_front_6, v_back_6, v_front_11, v_back_11, v_front_16, v_back_16 = G(v_front_1, v_back_1, v_front_6, v_back_6, v_front_11, v_back_11, v_front_16, v_back_16, m_front[s[9]], m_back[s[9]], m_front[s[10]], m_back[s[10]])
		do
			x_front, x_back = m_front[s[9]], m_back[s[9]]
			y_front, y_back = m_front[s[10]], m_back[s[10]]

			temp = v_back_1 + v_back_6 + x_back
			v_front_1 = v_front_1 + v_front_6 + x_front + temp // 2 ^ 32
			v_back_1 = temp % 2 ^ 32

			temp = v_front_16
			v_front_16 = bit32.bxor(v_back_16, v_back_1)
			v_back_16 = bit32.bxor(temp, v_front_1)

			temp = v_back_11 + v_back_16
			v_front_11 = v_front_11 + v_front_16 + temp // 2 ^ 32
			v_back_11 = temp % 2 ^ 32

			temp = v_front_6
			v_front_6 = bit32.bxor(bit32.rshift(v_front_6, 24), bit32.lshift(v_back_6, 8), bit32.rshift(v_front_11, 24), bit32.lshift(v_back_11, 8))
			v_back_6 = bit32.bxor(bit32.rshift(v_back_6, 24), bit32.lshift(temp, 8), bit32.rshift(v_back_11, 24), bit32.lshift(v_front_11, 8))

			temp = v_back_1 + v_back_6 + y_back
			v_front_1 = v_front_1 + v_front_6 + y_front + temp // 2 ^ 32
			v_back_1 = temp % 2 ^ 32

			temp = v_front_16
			v_front_16 = bit32.bxor(bit32.rshift(v_front_16, 16), bit32.lshift(v_back_16, 16), bit32.rshift(v_front_1, 16), bit32.lshift(v_back_1, 16))
			v_back_16 = bit32.bxor(bit32.rshift(v_back_16, 16), bit32.lshift(temp, 16), bit32.rshift(v_back_1, 16), bit32.lshift(v_front_1, 16))

			temp = v_back_11 + v_back_16
			v_front_11 = v_front_11 + v_front_16 + temp // 2 ^ 32
			v_back_11 = temp % 2 ^ 32

			temp = v_front_6
			v_front_6 = bit32.bxor(bit32.lshift(v_front_6, 1), bit32.rshift(v_back_6, 31), bit32.lshift(v_front_11, 1), bit32.rshift(v_back_11, 31))
			v_back_6 = bit32.bxor(bit32.lshift(v_back_6, 1), bit32.rshift(temp, 31), bit32.lshift(v_back_11, 1), bit32.rshift(v_front_11, 31))
		end

		-- v_front_2, v_back_2, v_front_7, v_back_7, v_front_12, v_back_12, v_front_13, v_back_13 = G(v_front_2, v_back_2, v_front_7, v_back_7, v_front_12, v_back_12, v_front_13, v_back_13, m_front[s[11]], m_back[s[11]], m_front[s[12]], m_back[s[12]])
		do
			x_front, x_back = m_front[s[11]], m_back[s[11]]
			y_front, y_back = m_front[s[12]], m_back[s[12]]

			temp = v_back_2 + v_back_7 + x_back
			v_front_2 = v_front_2 + v_front_7 + x_front + temp // 2 ^ 32
			v_back_2 = temp % 2 ^ 32

			temp = v_front_13
			v_front_13 = bit32.bxor(v_back_13, v_back_2)
			v_back_13 = bit32.bxor(temp, v_front_2)

			temp = v_back_12 + v_back_13
			v_front_12 = v_front_12 + v_front_13 + temp // 2 ^ 32
			v_back_12 = temp % 2 ^ 32

			temp = v_front_7
			v_front_7 = bit32.bxor(bit32.rshift(v_front_7, 24), bit32.lshift(v_back_7, 8), bit32.rshift(v_front_12, 24), bit32.lshift(v_back_12, 8))
			v_back_7 = bit32.bxor(bit32.rshift(v_back_7, 24), bit32.lshift(temp, 8), bit32.rshift(v_back_12, 24), bit32.lshift(v_front_12, 8))

			temp = v_back_2 + v_back_7 + y_back
			v_front_2 = v_front_2 + v_front_7 + y_front + temp // 2 ^ 32
			v_back_2 = temp % 2 ^ 32

			temp = v_front_13
			v_front_13 = bit32.bxor(bit32.rshift(v_front_13, 16), bit32.lshift(v_back_13, 16), bit32.rshift(v_front_2, 16), bit32.lshift(v_back_2, 16))
			v_back_13 = bit32.bxor(bit32.rshift(v_back_13, 16), bit32.lshift(temp, 16), bit32.rshift(v_back_2, 16), bit32.lshift(v_front_2, 16))

			temp = v_back_12 + v_back_13
			v_front_12 = v_front_12 + v_front_13 + temp // 2 ^ 32
			v_back_12 = temp % 2 ^ 32

			temp = v_front_7
			v_front_7 = bit32.bxor(bit32.lshift(v_front_7, 1), bit32.rshift(v_back_7, 31), bit32.lshift(v_front_12, 1), bit32.rshift(v_back_12, 31))
			v_back_7 = bit32.bxor(bit32.lshift(v_back_7, 1), bit32.rshift(temp, 31), bit32.lshift(v_back_12, 1), bit32.rshift(v_front_12, 31))
		end

		-- v_front_3, v_back_3, v_front_8, v_back_8, v_front_9, v_back_9, v_front_14, v_back_14 = G(v_front_3, v_back_3, v_front_8, v_back_8, v_front_9, v_back_9, v_front_14, v_back_14, m_front[s[13]], m_back[s[13]], m_front[s[14]], m_back[s[14]])
		do
			x_front, x_back = m_front[s[13]], m_back[s[13]]
			y_front, y_back = m_front[s[14]], m_back[s[14]]

			temp = v_back_3 + v_back_8 + x_back
			v_front_3 = v_front_3 + v_front_8 + x_front + temp // 2 ^ 32
			v_back_3 = temp % 2 ^ 32

			temp = v_front_14
			v_front_14 = bit32.bxor(v_back_14, v_back_3)
			v_back_14 = bit32.bxor(temp, v_front_3)

			temp = v_back_9 + v_back_14
			v_front_9 = v_front_9 + v_front_14 + temp // 2 ^ 32
			v_back_9 = temp % 2 ^ 32

			temp = v_front_8
			v_front_8 = bit32.bxor(bit32.rshift(v_front_8, 24), bit32.lshift(v_back_8, 8), bit32.rshift(v_front_9, 24), bit32.lshift(v_back_9, 8))
			v_back_8 = bit32.bxor(bit32.rshift(v_back_8, 24), bit32.lshift(temp, 8), bit32.rshift(v_back_9, 24), bit32.lshift(v_front_9, 8))

			temp = v_back_3 + v_back_8 + y_back
			v_front_3 = v_front_3 + v_front_8 + y_front + temp // 2 ^ 32
			v_back_3 = temp % 2 ^ 32

			temp = v_front_14
			v_front_14 = bit32.bxor(bit32.rshift(v_front_14, 16), bit32.lshift(v_back_14, 16), bit32.rshift(v_front_3, 16), bit32.lshift(v_back_3, 16))
			v_back_14 = bit32.bxor(bit32.rshift(v_back_14, 16), bit32.lshift(temp, 16), bit32.rshift(v_back_3, 16), bit32.lshift(v_front_3, 16))

			temp = v_back_9 + v_back_14
			v_front_9 = v_front_9 + v_front_14 + temp // 2 ^ 32
			v_back_9 = temp % 2 ^ 32

			temp = v_front_8
			v_front_8 = bit32.bxor(bit32.lshift(v_front_8, 1), bit32.rshift(v_back_8, 31), bit32.lshift(v_front_9, 1), bit32.rshift(v_back_9, 31))
			v_back_8 = bit32.bxor(bit32.lshift(v_back_8, 1), bit32.rshift(temp, 31), bit32.lshift(v_back_9, 1), bit32.rshift(v_front_9, 31))
		end

		-- v_front_4, v_back_4, v_front_5, v_back_5, v_front_10, v_back_10, v_front_15, v_back_15 = G(v_front_4, v_back_4, v_front_5, v_back_5, v_front_10, v_back_10, v_front_15, v_back_15, m_front[s[15]], m_back[s[15]], m_front[s[16]], m_back[s[16]])
		do
			x_front, x_back = m_front[s[15]], m_back[s[15]]
			y_front, y_back = m_front[s[16]], m_back[s[16]]

			temp = v_back_4 + v_back_5 + x_back
			v_front_4 = v_front_4 + v_front_5 + x_front + temp // 2 ^ 32
			v_back_4 = temp % 2 ^ 32

			temp = v_front_15
			v_front_15 = bit32.bxor(v_back_15, v_back_4)
			v_back_15 = bit32.bxor(temp, v_front_4)

			temp = v_back_10 + v_back_15
			v_front_10 = v_front_10 + v_front_15 + temp // 2 ^ 32
			v_back_10 = temp % 2 ^ 32

			temp = v_front_5
			v_front_5 = bit32.bxor(bit32.rshift(v_front_5, 24), bit32.lshift(v_back_5, 8), bit32.rshift(v_front_10, 24), bit32.lshift(v_back_10, 8))
			v_back_5 = bit32.bxor(bit32.rshift(v_back_5, 24), bit32.lshift(temp, 8), bit32.rshift(v_back_10, 24), bit32.lshift(v_front_10, 8))

			temp = v_back_4 + v_back_5 + y_back
			v_front_4 = v_front_4 + v_front_5 + y_front + temp // 2 ^ 32
			v_back_4 = temp % 2 ^ 32

			temp = v_front_15
			v_front_15 = bit32.bxor(bit32.rshift(v_front_15, 16), bit32.lshift(v_back_15, 16), bit32.rshift(v_front_4, 16), bit32.lshift(v_back_4, 16))
			v_back_15 = bit32.bxor(bit32.rshift(v_back_15, 16), bit32.lshift(temp, 16), bit32.rshift(v_back_4, 16), bit32.lshift(v_front_4, 16))

			temp = v_back_10 + v_back_15
			v_front_10 = v_front_10 + v_front_15 + temp // 2 ^ 32
			v_back_10 = temp % 2 ^ 32

			temp = v_front_5
			v_front_5 = bit32.bxor(bit32.lshift(v_front_5, 1), bit32.rshift(v_back_5, 31), bit32.lshift(v_front_10, 1), bit32.rshift(v_back_10, 31))
			v_back_5 = bit32.bxor(bit32.lshift(v_back_5, 1), bit32.rshift(temp, 31), bit32.lshift(v_back_10, 1), bit32.rshift(v_front_10, 31))
		end
	end

	h_front[1] = bit32.bxor(h_front[1], v_front_1, v_front_9)
	h_back[1] = bit32.bxor(h_back[1], v_back_1, v_back_9)

	h_front[2] = bit32.bxor(h_front[2], v_front_2, v_front_10)
	h_back[2] = bit32.bxor(h_back[2], v_back_2, v_back_10)

	h_front[3] = bit32.bxor(h_front[3], v_front_3, v_front_11)
	h_back[3] = bit32.bxor(h_back[3], v_back_3, v_back_11)

	h_front[4] = bit32.bxor(h_front[4], v_front_4, v_front_12)
	h_back[4] = bit32.bxor(h_back[4], v_back_4, v_back_12)

	h_front[5] = bit32.bxor(h_front[5], v_front_5, v_front_13)
	h_back[5] = bit32.bxor(h_back[5], v_back_5, v_back_13)

	h_front[6] = bit32.bxor(h_front[6], v_front_6, v_front_14)
	h_back[6] = bit32.bxor(h_back[6], v_back_6, v_back_14)

	h_front[7] = bit32.bxor(h_front[7], v_front_7, v_front_15)
	h_back[7] = bit32.bxor(h_back[7], v_back_7, v_back_15)

	h_front[8] = bit32.bxor(h_front[8], v_front_8, v_front_16)
	h_back[8] = bit32.bxor(h_back[8], v_back_8, v_back_16)
end

--[=[
	Calculates the BLAKE2b hash of `message` and emits it as a hash of `outSize`
	bytes. If provided, `key` is used as a [pepper][Pepper] for the hash.

	@param message The string to perform the hashing algorithm on
	@param outSize The length of the returned value in bytes. Must be between 1 and 64.
	@param key A pepper to use when performing the hashing algorithm. Must be no longer than 64 bytes.

	@return The computed hash as a hexadecimal string. This string will be `outSize * 2` bytes long.

	[Pepper]: https://en.wikipedia.org/wiki/Pepper_(cryptography)
]=]
local function blake2b(message: string, outSize: number, key: string?): string
	local rKey = if key then key else ""
	local keyLen = #rKey

	assert(math.clamp(outSize, 1, 64) == outSize, "outSize must be in the range [1, 64]")
	assert(math.clamp(keyLen, 0, 64) == keyLen, "key length must be in the range [0, 64]")

	local h_front = table.clone(IV_FRONT)
	local h_back = table.clone(IV_BACK)
	h_back[1] = bit32.bxor(h_back[1], 0x0101_0000, bit32.lshift(keyLen, 8), outSize)

	local block_front = table.create(16)
	local block_back = table.create(16)
	local messageLen = #message
	local t = if keyLen > 0 then 128 else 0

	if keyLen > 0 then
		-- This is technically bad for performance, but I don't really care
		-- since it happens at most once per hash
		rKey ..= string.rep("\0", -keyLen + 128)
		local j = 1
		for i = 1, 16 do
			local h, g, f, e, d, c, b, a = string.byte(rKey, j, j + 7)
			block_front[i] = bit32.bor(bit32.lshift(a, 24), bit32.lshift(b, 16), bit32.lshift(c, 8), d)
			block_back[i] = bit32.bor(bit32.lshift(e, 24), bit32.lshift(f, 16), bit32.lshift(g, 8), h)
			j += 8
		end
		-- Special case: is there even a message?
		F(h_front, h_back, block_front, block_back, t, messageLen == 0)
	end

	local leftover = messageLen % 128
	-- If the message is an even multiple of 128, we need to cut ourselves short
	-- so that we don't skip the final block or process an empty one
	local offset = if leftover == 0 then 128 else leftover
	for i = 1, messageLen - offset, 128 do
		for j = 1, 16 do
			local h, g, f, e, d, c, b, a = string.byte(message, i, i + 7)
			block_front[j] = bit32.bor(bit32.lshift(a, 24), bit32.lshift(b, 16), bit32.lshift(c, 8), d)
			block_back[j] = bit32.bor(bit32.lshift(e, 24), bit32.lshift(f, 16), bit32.lshift(g, 8), h)
			i += 8
		end
		t += 128
		F(h_front, h_back, block_front, block_back, t, false)
	end

	-- We have to push at least one block, regardless of message length
	-- However, if there's a key and the message is empty, we need to skip it
	if keyLen == 0 or messageLen > 0 then
		-- TODO find out if this has a significant performance cost
		local messageSnippet = string.sub(message, -offset)
		local finalMessage = messageSnippet .. string.rep("\0", 128)

		local n = 0
		local i = 1
		for j = 1, 16 do
			n += 1
			local h, g, f, e, d, c, b, a = string.byte(finalMessage, i, i + 7)
			block_front[j] = bit32.bor(bit32.lshift(a, 24), bit32.lshift(b, 16), bit32.lshift(c, 8), d)
			block_back[j] = bit32.bor(bit32.lshift(e, 24), bit32.lshift(f, 16), bit32.lshift(g, 8), h)
			i += 8
		end
		F(h_front, h_back, block_front, block_back, t + #messageSnippet, true)
	end

	local digest = string.format(
		"%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x",
		bit32.byteswap(h_back[1]),
		bit32.byteswap(h_front[1]),
		bit32.byteswap(h_back[2]),
		bit32.byteswap(h_front[2]),
		bit32.byteswap(h_back[3]),
		bit32.byteswap(h_front[3]),
		bit32.byteswap(h_back[4]),
		bit32.byteswap(h_front[4]),
		bit32.byteswap(h_back[5]),
		bit32.byteswap(h_front[5]),
		bit32.byteswap(h_back[6]),
		bit32.byteswap(h_front[6]),
		bit32.byteswap(h_back[7]),
		bit32.byteswap(h_front[7]),
		bit32.byteswap(h_back[8]),
		bit32.byteswap(h_front[8])
	)

	return string.sub(digest, 1, outSize * 2)
end

return blake2b


--!strict
--!native
--!optimize 2

--- The initial values used for the hasher state.
local IV = { 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19 }

--- A list of offsets to use for each round in the compression algorithm.
local SIGMA = {
	{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 },
	{ 15, 11, 5, 9, 10, 16, 14, 7, 2, 13, 1, 3, 12, 8, 6, 4 },
	{ 12, 9, 13, 1, 6, 3, 16, 14, 11, 15, 4, 7, 8, 2, 10, 5 },
	{ 8, 10, 4, 2, 14, 13, 12, 15, 3, 7, 6, 11, 5, 1, 16, 9 },
	{ 10, 1, 6, 8, 3, 5, 11, 16, 15, 2, 12, 13, 7, 9, 4, 14 },
	{ 3, 13, 7, 11, 1, 12, 9, 4, 5, 14, 8, 6, 16, 15, 2, 10 },
	{ 13, 6, 2, 16, 15, 14, 5, 11, 1, 8, 7, 4, 10, 3, 9, 12 },
	{ 14, 12, 8, 15, 13, 2, 4, 10, 6, 1, 16, 5, 9, 7, 3, 11 },
	{ 7, 16, 15, 10, 12, 4, 1, 9, 13, 3, 14, 8, 2, 5, 11, 6 },
	{ 11, 3, 9, 5, 8, 7, 2, 6, 16, 12, 10, 15, 4, 13, 14, 1 },
}

--[=[
	The BLAKE2s compression function.

	@param h The internal digest for the algorithm
	@param m A block of bytes utilized for rounds
	@param t A counter for how many bytes fed into the hasher so far, including this set
	@param f Whether or not this call is the final one
]=]
local function F(h: { number }, m: { number }, t: number, f: boolean)
	-- Conventional wisdom is to use an array here but that's awful for
	-- performance. So, we do it with 16 variables.
	-- Registries suffer but that's a small price to pay.
	local v01, v02, v03, v04, v05, v06, v07, v08 = h[1], h[2], h[3], h[4], h[5], h[6], h[7], h[8]
	local v09, v10, v11, v12, v13, v14, v15, v16 = IV[1], IV[2], IV[3], IV[4], IV[5], IV[6], IV[7], IV[8]
	v13 = bit32.bxor(v13, t)
	-- this is not using an rshift because `t` might be very big
	v14 = bit32.bxor(v14, t // 2 ^ 32)
	if f then
		v15 = bit32.bnot(v15)
	end

	-- G is a mixer function for the lanes of a block.
	-- It's defined inline here for performance.
	for _, s in SIGMA do
		-- G(v, 1, 5, 9, 13, m[s[1]], m[s[2]])
		v01 += v05 + m[s[1]]
		v13 = bit32.rrotate(bit32.bxor(v13, v01), 16)
		v09 += v13
		v05 = bit32.rrotate(bit32.bxor(v05, v09), 12)
		v01 += v05 + m[s[2]]
		v13 = bit32.rrotate(bit32.bxor(v13, v01), 8)
		v09 += v13
		v05 = bit32.rrotate(bit32.bxor(v05, v09), 7)

		-- G(v, 2, 6, 10, 14, m[s[3]], m[s[4]])
		v02 += v06 + m[s[3]]
		v14 = bit32.rrotate(bit32.bxor(v14, v02), 16)
		v10 += v14
		v06 = bit32.rrotate(bit32.bxor(v06, v10), 12)
		v02 += v06 + m[s[4]]
		v14 = bit32.rrotate(bit32.bxor(v14, v02), 8)
		v10 += v14
		v06 = bit32.rrotate(bit32.bxor(v06, v10), 7)

		-- G(v, 3, 7, 11, 15, m[s[5]], m[s[6]])
		v03 += v07 + m[s[5]]
		v15 = bit32.rrotate(bit32.bxor(v15, v03), 16)
		v11 += v15
		v07 = bit32.rrotate(bit32.bxor(v07, v11), 12)
		v03 += v07 + m[s[6]]
		v15 = bit32.rrotate(bit32.bxor(v15, v03), 8)
		v11 += v15
		v07 = bit32.rrotate(bit32.bxor(v07, v11), 7)

		-- G(v, 4, 8, 12, 16, m[s[7]], m[s[8]])
		v04 += v08 + m[s[7]]
		v16 = bit32.rrotate(bit32.bxor(v16, v04), 16)
		v12 += v16
		v08 = bit32.rrotate(bit32.bxor(v08, v12), 12)
		v04 += v08 + m[s[8]]
		v16 = bit32.rrotate(bit32.bxor(v16, v04), 8)
		v12 += v16
		v08 = bit32.rrotate(bit32.bxor(v08, v12), 7)

		-- G(v, 1, 6, 11, 16, m[s[9]], m[s[10]])
		v01 += v06 + m[s[9]]
		v16 = bit32.rrotate(bit32.bxor(v16, v01), 16)
		v11 += v16
		v06 = bit32.rrotate(bit32.bxor(v06, v11), 12)
		v01 += v06 + m[s[10]]
		v16 = bit32.rrotate(bit32.bxor(v16, v01), 8)
		v11 += v16
		v06 = bit32.rrotate(bit32.bxor(v06, v11), 7)

		-- G(v, 2, 7, 12, 13, m[s[11]], m[s[12]])
		v02 += v07 + m[s[11]]
		v13 = bit32.rrotate(bit32.bxor(v13, v02), 16)
		v12 += v13
		v07 = bit32.rrotate(bit32.bxor(v07, v12), 12)
		v02 += v07 + m[s[12]]
		v13 = bit32.rrotate(bit32.bxor(v13, v02), 8)
		v12 += v13
		v07 = bit32.rrotate(bit32.bxor(v07, v12), 7)

		-- G(v, 3, 8, 9, 14, m[s[13]], m[s[14]])
		v03 += v08 + m[s[13]]
		v14 = bit32.rrotate(bit32.bxor(v14, v03), 16)
		v09 += v14
		v08 = bit32.rrotate(bit32.bxor(v08, v09), 12)
		v03 += v08 + m[s[14]]
		v14 = bit32.rrotate(bit32.bxor(v14, v03), 8)
		v09 += v14
		v08 = bit32.rrotate(bit32.bxor(v08, v09), 7)

		-- G(v, 4, 5, 10, 15, m[s[15]], m[s[16]])
		v04 += v05 + m[s[15]]
		v15 = bit32.rrotate(bit32.bxor(v15, v04), 16)
		v10 += v15
		v05 = bit32.rrotate(bit32.bxor(v05, v10), 12)
		v04 += v05 + m[s[16]]
		v15 = bit32.rrotate(bit32.bxor(v15, v04), 8)
		v10 += v15
		v05 = bit32.rrotate(bit32.bxor(v05, v10), 7)
	end

	h[1] = bit32.bxor(h[1], v01, v09)
	h[2] = bit32.bxor(h[2], v02, v10)
	h[3] = bit32.bxor(h[3], v03, v11)
	h[4] = bit32.bxor(h[4], v04, v12)
	h[5] = bit32.bxor(h[5], v05, v13)
	h[6] = bit32.bxor(h[6], v06, v14)
	h[7] = bit32.bxor(h[7], v07, v15)
	h[8] = bit32.bxor(h[8], v08, v16)
end

--[=[
	Calculates the BLAKE2s hash of `message` and emits it as a hash of `outSize`
	bytes. If provided, `key` is used as a [pepper][Pepper] for the hash.

	@param message The string to perform the hashing algorithm on
	@param outSize The length of the returned value in bytes. Must be between 1 and 32.
	@param key A pepper to use when performing the hashing algorithm. Must be no longer than 32 bytes.

	@return The computed hash as a hexadecimal string. This string will be `outSize * 2` bytes long.

	[Pepper]: https://en.wikipedia.org/wiki/Pepper_(cryptography)
]=]
local function blake2s(message: string, outSize: number, key: string?): string
	local rKey = if key then key else ""
	local keyLen = #rKey

	assert(math.clamp(outSize, 1, 32) == outSize, "outSize must be in the range [1, 32]")
	assert(math.clamp(keyLen, 0, 32) == keyLen, "key length must be in the range [0, 32]")

	local h = table.clone(IV)
	h[1] = bit32.bxor(h[1], 0x0101_0000, bit32.lshift(keyLen, 8), outSize)

	local block = table.create(16)
	local messageLen = #message
	local t = if keyLen > 0 then 64 else 0

	if keyLen > 0 then
		-- This is technically bad for performance, but I don't really care
		-- since it happens at most once per hash
		rKey ..= string.rep("\0", -keyLen + 64)
		local j = 1
		for i = 1, 16 do
			local d, c, b, a = string.byte(rKey, j, j + 3)
			block[i] = bit32.bor(bit32.lshift(a, 24), bit32.lshift(b, 16), bit32.lshift(c, 8), d)
			j += 4
		end
		-- Special case: is there even a message?
		F(h, block, 64, messageLen == 0)
	end

	local leftover = messageLen % 64
	-- If the message is an even multiple of 64, we need to cut ourselves short
	-- so that we don't skip the final block or process an empty one
	local offset = if leftover == 0 then 64 else leftover
	for i = 1, messageLen - offset, 64 do
		for j = 1, 16 do
			local d, c, b, a = string.byte(message, i, i + 3)
			block[j] = bit32.bor(bit32.lshift(a, 24), bit32.lshift(b, 16), bit32.lshift(c, 8), d)
			i += 4
		end
		t += 64
		F(h, block, t, false)
	end

	-- We have to push at least one block, regardless of message length
	-- However, if there's a key and the message is empty, we need to skip it
	if keyLen == 0 or messageLen > 0 then
		-- TODO find out if this has a significant performance cost
		local messageSnippet = string.sub(message, -offset)
		local finalMessage = messageSnippet .. string.rep("\0", 64)

		local i = 1
		for j = 1, 16 do
			local d, c, b, a = string.byte(finalMessage, i, i + 3)
			block[j] = bit32.bor(bit32.lshift(a, 24), bit32.lshift(b, 16), bit32.lshift(c, 8), d)
			i += 4
		end
		F(h, block, t + #messageSnippet, true)
	end

	-- TODO efficiency
	local digest = string.format(
		"%08x%08x%08x%08x%08x%08x%08x%08x",
		bit32.byteswap(h[1]),
		bit32.byteswap(h[2]),
		bit32.byteswap(h[3]),
		bit32.byteswap(h[4]),
		bit32.byteswap(h[5]),
		bit32.byteswap(h[6]),
		bit32.byteswap(h[7]),
		bit32.byteswap(h[8])
	)

	return string.sub(digest, 1, outSize * 2)
end

return blake2s


--!strict
--!native
--!optimize 2

--- Precomputed MD5 constants. You may compute these manually
--- with `floor(2^32 * abs(sin(i + 1)))` from 0 to 63.
--stylua: ignore
local K = {
    0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
    0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
    0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
    0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
    0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
    0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
    0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
    0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
    0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
    0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
    0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
    0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
    0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
    0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
    0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
    0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391,
}

--- The order in which lrotates happen in the core loops.
--stylua: ignore
local SHIFT_ORDER = {
    7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
    5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
    4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
    6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,
}

--- An allocation that is reused for blocks in the compression function.
local BLOCK = table.create(64)

--- Processes `message` from byte `start` to byte `finish` using `digest`.
--- It is assumed that `(finish - start) % 64 == 0`.
local function processBlocks(digest: { number }, message: string, start: number, finish: number)
	local block = BLOCK
	local d1, d2, d3, d4 = digest[1], digest[2], digest[3], digest[4]

	for i = start, finish, 64 do
		-- Fill `block`
		for t = 1, 16 do
			-- But what if I were to simply construct an integer in reverse
			-- order? Oh hoho! Delightfully devilish, Seymour!
			local d, c, b, a = string.byte(message, i, i + 3)
			block[t] = bit32.bor(bit32.lshift(a, 24), bit32.lshift(b, 16), bit32.lshift(c, 8), d)
			i += 4
		end

		-- Standard MD5 rounds. It's quite a bit faster if these are unrolled,
		-- but I don't want to. This module is already very fast.
		local a, b, c, d = d1, d2, d3, d4
		local d_temp, func = 0, 0
		for j = 0, 15 do
			func = bit32.bxor(d, bit32.band(b, bit32.bxor(c, d)))
			d_temp = d
			d = c
			c = b
			b = b + bit32.lrotate(a + func + K[j + 1] + block[j + 1], SHIFT_ORDER[j + 1])
			a = d_temp
		end

		for j = 16, 31 do
			func = bit32.bxor(c, bit32.band(d, bit32.bxor(b, c)))
			d_temp = d
			d = c
			c = b
			b = b + bit32.lrotate(a + func + K[j + 1] + block[(5 * j + 1) % 16 + 1], SHIFT_ORDER[j + 1])
			a = d_temp
		end

		for j = 32, 47 do
			func = bit32.bxor(b, c, d)
			d_temp = d
			d = c
			c = b
			b = b + bit32.lrotate(a + func + K[j + 1] + block[(3 * j + 5) % 16 + 1], SHIFT_ORDER[j + 1])
			a = d_temp
		end

		for j = 48, 63 do
			func = bit32.bxor(c, bit32.bor(b, bit32.bnot(d)))
			d_temp = d
			d = c
			c = b
			b = b + bit32.lrotate(a + func + K[j + 1] + block[(7 * j) % 16 + 1], SHIFT_ORDER[j + 1])
			a = d_temp
		end

		d1 = bit32.bor(a + d1)
		d2 = bit32.bor(b + d2)
		d3 = bit32.bor(c + d3)
		d4 = bit32.bor(d + d4)
	end

	digest[1] = d1
	digest[2] = d2
	digest[3] = d3
	digest[4] = d4
end

--[=[
	Computes the MD5 hash for `message` and returns it.
	The function returns both a hexadecimal encoded string of the hash
	and a **read-only** table containing the four 32-bit integers that
	make the hash up.

	@param message -- The payload to compute the MD5 hash of.
	@return string -- The computed MD5 hash as a string of hexadecimal digits
	@return {number} -- The computed MD5 hash as a read-only array of 4 integers
]=]
local function md5(message: string): (string, { number })
	local digest = {
		0x67452301,
		0xefcdab89,
		0x98badcfe,
		0x10325476,
	}

	local messageLen = #message
	local leftover = messageLen % 64
	if messageLen >= 64 then
		processBlocks(digest, message, 1, messageLen - leftover)
	end

	-- Raise `leftover` to next multiple of 64 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 nextMultipleOf64 = bit32.band(leftover + 32, 0xffffffc0)

	local finalMessage = {
		if leftover ~= 0 then string.sub(message, -leftover) else "",
		"\x80",
		string.rep("\0", (nextMultipleOf64 - leftover - 9) % 64),
		string.pack("<L", messageLen * 8),
	}
	local finalBlock = table.concat(finalMessage)
	processBlocks(digest, finalBlock, 1, #finalBlock)

	digest[1] = bit32.byteswap(digest[1])
	digest[2] = bit32.byteswap(digest[2])
	digest[3] = bit32.byteswap(digest[3])
	digest[4] = bit32.byteswap(digest[4])

	return string.format("%08x%08x%08x%08x", digest[1], digest[2], digest[3], digest[4]), table.freeze(digest)
end

return md5


--!strict
--!native
--!optimize 2

--- An allocation that is reused for blocks during hashing
local BLOCK = table.create(80)

--[=[
	Processes `message` from byte `start` to byte `finish` using `digest`.
	It is assumed that `(finish - start) % 64 == 0`.
]=]
local function processBlocks(digest: { number }, message: string, start: number, finish: number)
	local block = BLOCK
	local d1, d2, d3, d4, d5 = digest[1], digest[2], digest[3], digest[4], digest[5]

	for i = start, finish, 64 do
		-- Fill `block`
		for t = 1, 16 do
			local a, b, c, d = string.byte(message, i, i + 3)
			block[t] = bit32.bor(bit32.lshift(a, 24), bit32.lshift(b, 16), bit32.lshift(c, 8), d)
			i += 4
		end
		-- This loop was previously unrolled but with improvements to Luau,
		-- the performance boost is now minimal.
		for t = 17, 80 do
			block[t] = bit32.lrotate(bit32.bxor(block[t - 3], block[t - 8], block[t - 14], block[t - 16]), 1)
		end

		-- Standard SHA1 rounds
		local a, b, c, d, e = d1, d2, d3, d4, d5
		local temp
		for t = 1, 20 do
			-- F(B,C,D) = CH(X,Y,Z) = ternary for bits
			-- e.g. if x_bit == 1 then bit = y_bit else bit = z_bit
			-- reference implemention, this is: (X AND Y) XOR ((NOT X) AND Z)
			-- However, the XOR is equivalent to addition if you don't carry
			-- Since we're using bit32 (which truncates), we don't carry.
			-- So we're left with:
			-- (X AND Y) + ((NOT X) AND Z)
			-- Also, NOT X is equivalent to -1 - X
			-- So we can drop another bitwise op:

			-- f = bit32.band(b, c) + bit32.band(-1 - b, d)
			temp = bit32.lrotate(a, 5) + bit32.band(b, c) + bit32.band(bit32.bnot(b), d) + e + 0x5A827999 + block[t]
			e, d, c, b, a = d, c, bit32.lrotate(b, 30), a, temp
		end
		for t = 21, 40 do
			-- f = bit32.bxor(b, c, d)
			temp = bit32.lrotate(a, 5) + bit32.bxor(b, c, d) + e + 0x6ED9EBA1 + block[t]
			e, d, c, b, a = d, c, bit32.lrotate(b, 30), a, temp
		end
		for t = 41, 60 do
			-- f = bit32.band(d, c) + bit32.band(b, bit32.bxor(d, c))
			temp = bit32.lrotate(a, 5) + bit32.band(d, c) + bit32.band(b, bit32.bxor(d, c)) + e + 0x8F1BBCDC + block[t]
			e, d, c, b, a = d, c, bit32.lrotate(b, 30), a, temp
		end
		for t = 61, 80 do
			-- f = bit32.bxor(b, c, d)
			temp = bit32.lrotate(a, 5) + bit32.bxor(b, c, d) + e + 0xCA62C1D6 + block[t]
			e, d, c, b, a = d, c, bit32.lrotate(b, 30), a, temp
		end

		d1 = bit32.bor(a + d1, 0)
		d2 = bit32.bor(b + d2, 0)
		d3 = bit32.bor(c + d3, 0)
		d4 = bit32.bor(d + d4, 0)
		d5 = bit32.bor(e + d5, 0)
	end

	-- Only update the digest once for the vine
	digest[1] = d1
	digest[2] = d2
	digest[3] = d3
	digest[4] = d4
	digest[5] = d5
end

--[=[
	Computes the SHA-1 hash for `message` and returns it.
	This function returns both a hexadecimal encoded string of the hash
	and returns a **read-only** table containing the five 32-bit integers
	that make up the hash.

	@param message -- The payload to compute the SHA-1 hash of
	@return string -- The computed SHA-1 hash as a string of hexadecimal digits
	@return {number} -- The computed SHA-1 hash as an array of 5 integers
]=]
local function sha1(message: string): (string, { number })
	local digest = {
		0x67452301,
		0xEFCDAB89,
		0x98BADCFE,
		0x10325476,
		0xC3D2E1F0,
	}

	local messageLen = #message
	local leftover = messageLen % 64
	if messageLen >= 64 then
		processBlocks(digest, message, 1, messageLen - leftover)
	end

	-- Raise `leftover` to next multiple of 64 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 nextMultipleOf64 = bit32.band(leftover + 32, 0xffffffc0)

	local finalMessage = {
		if leftover ~= 0 then string.sub(message, -leftover) else "",
		"\x80",
		string.rep("\0", (nextMultipleOf64 - leftover - 9) % 64),
		string.pack(">L", messageLen * 8),
	}
	local finalBlock = table.concat(finalMessage)
	processBlocks(digest, finalBlock, 1, #finalBlock)

	return string.format("%08x%08x%08x%08x%08x", digest[1], digest[2], digest[3], digest[4], digest[5]),
		table.freeze(digest)
end

return sha1


--!strict
--!native
--!optimize 2

--stylua: ignore
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,
}

--- An allocation that is reused for blocks during hashing
local BLOCK = table.create(64)

--[=[
	Processes `message` from byte `start` to byte `finish` using `digest`.
	It is assumed that `(finish - start) % 64 == 0`.
]=]
local function processBlocks(digest: { number }, message: string, start: number, finish: number)
	local block = BLOCK
	local d1, d2, d3, d4 = digest[1], digest[2], digest[3], digest[4]
	local d5, d6, d7, d8 = digest[5], digest[6], digest[7], digest[8]

	for i = start, finish, 64 do
		-- Fill `block`
		for t = 1, 16 do
			local a, b, c, d = string.byte(message, i, i + 3)
			block[t] = bit32.bor(bit32.lshift(a, 24), bit32.lshift(b, 16), bit32.lshift(c, 8), d)
			i += 4
		end
		for t = 17, 64 do
			local b2 = block[t - 2]
			local b15 = block[t - 15]
			block[t] = bit32.bxor(bit32.rrotate(b2, 17), bit32.rrotate(b2, 19), bit32.rshift(b2, 10))
				+ block[t - 7]
				+ bit32.bxor(bit32.rrotate(b15, 7), bit32.rrotate(b15, 18), bit32.rshift(b15, 3))
				+ block[t - 16]
		end

		local a, b, c, d = d1, d2, d3, d4
		local e, f, g, h = d5, d6, d7, d8
		local temp1, temp2
		for t = 1, 64 do
			-- h + bsig1(e) + ch(e, f, g) + K[t] + block[t]
			-- bsig1(e) = bit32.bxor(bit32.rrotate(e, 6), bit32.rrotate(e, 11), bit32.rrotate(e, 25))
			-- ch(e, f, g) = bit32.band(e, f) + bit32.band(-1 - e, g)
			temp1 = h
				+ bit32.bxor(bit32.rrotate(e, 6), bit32.rrotate(e, 11), bit32.rrotate(e, 25))
				+ bit32.band(e, f)
				+ bit32.band(bit32.bnot(e), g)
				+ K[t]
				+ block[t]
			-- bsig0(a) + maj(a, b, c)
			-- bsig0(a) = bit32.bxor(bit32.rrotate(a, 2), bit32.rrotate(a, 13), bit32.rrotate(a, 22))
			-- maj(a, b, c) = bit32.band(c, b) + bit32.band(a, bit32.bxor(c, b))
			temp2 = bit32.band(c, b)
				+ bit32.band(a, bit32.bxor(c, b))
				+ bit32.bxor(bit32.rrotate(a, 2), bit32.rrotate(a, 13), bit32.rrotate(a, 22))
			h = g
			g = f
			f = e
			e = d + temp1
			d = c
			c = b
			b = a
			a = temp1 + temp2
		end

		d1 = bit32.bor(a + d1, 0)
		d2 = bit32.bor(b + d2, 0)
		d3 = bit32.bor(c + d3, 0)
		d4 = bit32.bor(d + d4, 0)
		d5 = bit32.bor(e + d5, 0)
		d6 = bit32.bor(f + d6, 0)
		d7 = bit32.bor(g + d7, 0)
		d8 = bit32.bor(h + d8, 0)
	end

	digest[1] = d1
	digest[2] = d2
	digest[3] = d3
	digest[4] = d4
	digest[5] = d5
	digest[6] = d6
	digest[7] = d7
	digest[8] = d8
end

--[=[
	Computes the SHA-224 hash for `message` and returns it.
	This function returns both a hexadecimal encoded string of the hash
	and returns a **read-only** table containing the seven 32-bit integers
	that make up the hash.

	@param message -- The payload to compute the SHA-224 hash of
	@return string -- The computed SHA-224 hash as a string of hexadecimal digits
	@return {number} -- The computed SHA-224 hash as an array of 7 integers
]=]
local function sha224(message: string): (string, { number })
	local digest = {
		0xc1059ed8,
		0x367cd507,
		0x3070dd17,
		0xf70e5939,
		0xffc00b31,
		0x68581511,
		0x64f98fa7,
		0xbefa4fa4,
	}

	local messageLen = #message
	local leftover = messageLen % 64
	if messageLen >= 64 then
		processBlocks(digest, message, 1, messageLen - leftover)
	end

	-- Raise `leftover` to next multiple of 64 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 nextMultipleOf64 = bit32.band(leftover + 32, 0xffffffc0)

	local finalMessage = {
		if leftover ~= 0 then string.sub(message, -leftover) else "",
		"\x80",
		string.rep("\0", (nextMultipleOf64 - leftover - 9) % 64),
		string.pack(">L", messageLen * 8),
	}
	local finalBlock = table.concat(finalMessage)
	processBlocks(digest, finalBlock, 1, #finalBlock)

	digest[8] = nil

	return string.format(
		"%08x%08x%08x%08x%08x%08x%08x",
		digest[1],
		digest[2],
		digest[3],
		digest[4],
		digest[5],
		digest[6],
		digest[7]
	),
		table.freeze(digest)
end

return sha224


--!strict
--!native
--!optimize 2

--stylua: ignore
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,
}

--- An allocation that is reused for blocks during hashing
local BLOCK = table.create(64)

--[=[
	Processes `message` from byte `start` to byte `finish` using `digest`.
	It is assumed that `(finish - start) % 64 == 0`.
]=]
local function processBlocks(digest: { number }, message: string, start: number, finish: number)
	local block = BLOCK
	local d1, d2, d3, d4 = digest[1], digest[2], digest[3], digest[4]
	local d5, d6, d7, d8 = digest[5], digest[6], digest[7], digest[8]

	for i = start, finish, 64 do
		-- Fill `block`
		for t = 1, 16 do
			local a, b, c, d = string.byte(message, i, i + 3)
			block[t] = bit32.bor(bit32.lshift(a, 24), bit32.lshift(b, 16), bit32.lshift(c, 8), d)
			i += 4
		end
		for t = 17, 64 do
			local b2 = block[t - 2]
			local b15 = block[t - 15]
			block[t] = bit32.bxor(bit32.rrotate(b2, 17), bit32.rrotate(b2, 19), bit32.rshift(b2, 10))
				+ block[t - 7]
				+ bit32.bxor(bit32.rrotate(b15, 7), bit32.rrotate(b15, 18), bit32.rshift(b15, 3))
				+ block[t - 16]
		end

		local a, b, c, d = d1, d2, d3, d4
		local e, f, g, h = d5, d6, d7, d8
		local temp1, temp2
		for t = 1, 64 do
			-- h + bsig1(e) + ch(e, f, g) + K[t] + block[t]
			-- bsig1(e) = bit32.bxor(bit32.rrotate(e, 6), bit32.rrotate(e, 11), bit32.rrotate(e, 25))
			-- ch(e, f, g) = bit32.band(e, f) + bit32.band(-1 - e, g)
			temp1 = h
				+ bit32.bxor(bit32.rrotate(e, 6), bit32.rrotate(e, 11), bit32.rrotate(e, 25))
				+ bit32.band(e, f)
				+ bit32.band(bit32.bnot(e), g)
				+ K[t]
				+ block[t]
			-- bsig0(a) + maj(a, b, c)
			-- bsig0(a) = bit32.bxor(bit32.rrotate(a, 2), bit32.rrotate(a, 13), bit32.rrotate(a, 22))
			-- maj(a, b, c) = bit32.band(c, b) + bit32.band(a, bit32.bxor(c, b))
			temp2 = bit32.band(c, b)
				+ bit32.band(a, bit32.bxor(c, b))
				+ bit32.bxor(bit32.rrotate(a, 2), bit32.rrotate(a, 13), bit32.rrotate(a, 22))
			h = g
			g = f
			f = e
			e = d + temp1
			d = c
			c = b
			b = a
			a = temp1 + temp2
		end

		d1 = bit32.bor(a + d1, 0)
		d2 = bit32.bor(b + d2, 0)
		d3 = bit32.bor(c + d3, 0)
		d4 = bit32.bor(d + d4, 0)
		d5 = bit32.bor(e + d5, 0)
		d6 = bit32.bor(f + d6, 0)
		d7 = bit32.bor(g + d7, 0)
		d8 = bit32.bor(h + d8, 0)
	end

	digest[1] = d1
	digest[2] = d2
	digest[3] = d3
	digest[4] = d4
	digest[5] = d5
	digest[6] = d6
	digest[7] = d7
	digest[8] = d8
end

--[=[
	Computes the SHA-256 hash for `message` and returns it.
	This function returns both a hexadecimal encoded string of the hash
	and returns a **read-only** table containing the eight 32-bit integers
	that make up the hash.

	@param message -- The payload to compute the SHA-256 hash of
	@return string -- The computed SHA-256 hash as a string of hexadecimal digits
	@return {number} -- The computed SHA-256 hash as an array of 8 integers
]=]
local function sha256(message: string): (string, { number })
	local digest = {
		0x6a09e667,
		0xbb67ae85,
		0x3c6ef372,
		0xa54ff53a,
		0x510e527f,
		0x9b05688c,
		0x1f83d9ab,
		0x5be0cd19,
	}

	local messageLen = #message
	local leftover = messageLen % 64
	if messageLen >= 64 then
		processBlocks(digest, message, 1, messageLen - leftover)
	end

	-- Raise `leftover` to next multiple of 64 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 nextMultipleOf64 = bit32.band(leftover + 32, 0xffff_ffc0)

	local finalMessage = {
		if leftover ~= 0 then string.sub(message, -leftover) else "",
		"\x80",
		string.rep("\0", (nextMultipleOf64 - leftover - 9) % 64),
		string.pack(">L", messageLen * 8),
	}
	local finalBlock = table.concat(finalMessage)
	processBlocks(digest, finalBlock, 1, #finalBlock)

	return string.format(
		"%08x%08x%08x%08x%08x%08x%08x%08x",
		digest[1],
		digest[2],
		digest[3],
		digest[4],
		digest[5],
		digest[6],
		digest[7],
		digest[8]
	),
		table.freeze(digest)
end

return sha256


--!strict
--!native
--!optimize 2

local PRIME_1 = 0x9e3779B1
local PRIME_2 = 0x85ebca77
local PRIME_3 = 0xc2B2ae3d
local PRIME_4 = 0x27d4eb2f
local PRIME_5 = 0x165667b1

--[=[
    Calculates `a * b` mod 32
]=]
local function mult(a: number, b: number): number
	local ah, al = bit32.rshift(a, 16), a % 2 ^ 16
	local bh, bl = bit32.rshift(b, 16), b % 2 ^ 16
	return bit32.lshift((ah * bl) + (al * bh), 16) + (al * bl)
end

--[=[
	Computes the XXH32 hash for `message` and returns it. If specified, `seed`
	is used as a seed for the hash. It defaults to `0`.

	This function returns the 32-bit integer that's the result of the hash.

	@param message -- The message to compute the XXHASH32 of
	@param seed -- The seed to use for the hashing algorithm (defaults to `0`)
]=]
local function xxhash32(message: string, seed: number?): number
	local rSeed = if seed == nil then 0 else bit32.bor(seed, 0)
	local messageLen = #message

	local wordC = 1

	local i = 1
	local digest
	if messageLen >= 16 then
		-- We don't need to worry about overflow for addition here
		local accum1 = rSeed + PRIME_1 + PRIME_2
		local accum2 = rSeed + PRIME_2
		local accum3 = rSeed
		local accum4 = rSeed - PRIME_1
		for _ = 0, messageLen - 16, 16 do
			--stylua: ignore start
			local d1, c1, b1, a1,
				d2, c2, b2, a2,
				d3, c3, b3, a3,
				d4, c4, b4, a4 = string.byte(message, i, i + 15)
			accum1 = mult(bit32.lrotate(accum1 + mult(bit32.bor(bit32.lshift(a1, 24), bit32.lshift(b1, 16), bit32.lshift(c1, 8), d1), PRIME_2), 13), PRIME_1)
			accum2 = mult(bit32.lrotate(accum2 + mult(bit32.bor(bit32.lshift(a2, 24), bit32.lshift(b2, 16), bit32.lshift(c2, 8), d2), PRIME_2), 13), PRIME_1)
			accum3 = mult(bit32.lrotate(accum3 + mult(bit32.bor(bit32.lshift(a3, 24), bit32.lshift(b3, 16), bit32.lshift(c3, 8), d3), PRIME_2), 13), PRIME_1)
			accum4 = mult(bit32.lrotate(accum4 + mult(bit32.bor(bit32.lshift(a4, 24), bit32.lshift(b4, 16), bit32.lshift(c4, 8), d4), PRIME_2), 13), PRIME_1)
			--stylua: ignore end
			wordC += 4
			i += 16
		end
		digest = bit32.lrotate(accum1, 1)
			+ bit32.lrotate(accum2, 7)
			+ bit32.lrotate(accum3, 12)
			+ bit32.lrotate(accum4, 18)
	else
		digest = bit32.bor(rSeed + PRIME_5, 0)
	end
	digest = bit32.bor(digest + messageLen, 0)

	for _ = wordC, messageLen // 4 do
		local d, c, b, a = string.byte(message, i, i + 3)
		--stylua: ignore
		digest += mult(bit32.bor(bit32.lshift(a, 24), bit32.lshift(b, 16), bit32.lshift(c, 8), d), PRIME_3)
		digest = mult(bit32.lrotate(digest, 17), PRIME_4)
		wordC += 1
		i += 4
	end

	for j = -(messageLen % 4), -1 do
		digest += mult(string.byte(message, j), PRIME_5)
		digest = mult(bit32.lrotate(digest, 11), PRIME_1)
	end

	digest = mult(bit32.bxor(digest, bit32.rshift(digest, 15)), PRIME_2)
	digest = mult(bit32.bxor(digest, bit32.rshift(digest, 13)), PRIME_3)
	digest = bit32.bxor(digest, bit32.rshift(digest, 16))

	return digest
end

return xxhash32


local blake2b = require("../modules/blake2b")

local t = os.clock()
local startgc = gcinfo()

assert(
	blake2b("abc", 64)
		== "ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923",
	"(BLAKE2b) abc hash does not match"
)

--stylua: ignore start
assert(blake2b("abc", 60) == "bdc0b8b38bd714f166d1cf227148c8de5a5ed75da184589212d8a584cccd73a02422f43c4dd1a427b8fbad742d348eb81d8cb7492efe626fce490618", "(BLAKE2b) 60 byte abc hash does not match")
assert(blake2b("abc", 56) == "13ee23af59cf24b95795d6417d2592f96d772eb6c4866e51698ecf6d4848539251ae2ee731a28758ecbcd5cb5f3f005c202f509cc32975b1", "(BLAKE2b) 56 byte abc hash does not match")
assert(blake2b("abc", 52) == "9a5803f1993120dfbf7af41936779ae2192dee216146b264e8080aae7dc602926e8d533565d775b43be548cb67c49aa7cde9e063", "(BLAKE2b) 52 byte abc hash does not match")
assert(blake2b("abc", 48) == "6f56a82c8e7ef526dfe182eb5212f7db9df1317e57815dbda46083fc30f54ee6c66ba83be64b302d7cba6ce15bb556f4", "(BLAKE2b) 48 byte abc hash does not match")
assert(blake2b("abc", 44) == "db1ccc0bf65b615c7269cf24b45ba65c665cb228ef77057ce4f9bec0b2821af9f9ffdd5441bd260ea1158084", "(BLAKE2b) 44 byte abc hash does not match")
assert(blake2b("abc", 40) == "8ad6d6166cdc8c2ffd5f25c5e7f957513b4a0e6661e998c3744a101363ac6e352858b0d412d5c322", "(BLAKE2b) 40 byte abc hash does not match")
assert(blake2b("abc", 36) == "b2b77f50103450a6aaed6b3b080dbe5bacde149c9587a0096149418264d1fee0b98dac2b", "(BLAKE2b) 36 byte abc hash does not match")
assert(blake2b("abc", 32) == "bddd813c634239723171ef3fee98579b94964e3bb1cb3e427262c8c068d52319", "(BLAKE2b) 32 byte abc hash does not match")
assert(blake2b("abc", 28) == "9bd237b02a29e43bdd6738afa5b53ff0eee178d6210b618e4511aec8", "(BLAKE2b) 28 byte abc hash does not match")
assert(blake2b("abc", 24) == "56a17e38cc371a46b12c32f18e0c61de2a84e9c2555b114e", "(BLAKE2b) 24 byte abc hash does not match")
assert(blake2b("abc", 16) == "cf4ab791c62b8d2b2109c90275287816", "(BLAKE2b) 16 byte abc hash does not match")
assert(blake2b("abc", 8) == "d8bb14d833d59559", "(BLAKE2b) 8 byte abc hash does not match")
assert(blake2b("abc", 4) == "63906248", "(BLAKE2b) 4 byte abc hash does not match")
assert(blake2b("abc", 2) == "ae1e", "(BLAKE2b) 2 byte abc hash does not match")
assert(blake2b("abc", 1) == "6b", "(BLAKE2b) 1 byte abc hash does not match")

-- No tricks up my sleeve: I'm pulling these keys from the hashes above

assert(blake2b("abc", 64, "ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d1")
	== "c2f2d6db0908b89406dfd636c55abd50f31be6b13767a6258f61e476aca87416f876ce17bd336c3d3f6b6a2652f42b8480c3edd1ae66657f587f925b87ab8416", "(BLAKE2b) abc with 64 byte key hash does not match")
assert(blake2b("abc", 64, "bdc0b8b38bd714f166d1cf227148c8de5a5ed75da184589212d8a584cccd")
	== "0a1af64a08b611489317551c2fab679ee167f522063f185dd9401abb9a98168fa28020d7a630d8f95890a9cde3d3caaee40e925b6ef4fbc6bef19b982b7afb07", "(BLAKE2b) abc with 60 byte key hash does not match")
assert(blake2b("abc", 64, "13ee23af59cf24b95795d6417d2592f96d772eb6c4866e51698ecf6d")
	== "5898daa9ef3e553095d750fb92c53d531350abdae57b590cd319ecc6dc8d0a41370e063344505f0016bf59b09ba5b5282f68830f5bbb40f117c74bfb678d6a5d", "(BLAKE2b) abc with 56 byte key hash does not match")
assert(blake2b("abc", 64, "6f56a82c8e7ef526dfe182eb5212f7db9df1317e57815dbd")
	== "be0a08a35fa7bac9f2441b52d41abc0b19981a14f1161575e70c28c18a7154edd2fb764cb45f17e87bec23a22a3454266b03777fb770be817c59ff80e349cd86", "(BLAKE2b) abc with 48 byte key hash does not match")
assert(blake2b("abc", 64, "db1ccc0bf65b615c7269cf24b45ba65c665cb228ef77")
	== "6dda971e92cd392f866c7442b585ca16a3b8e7e052b0cff9ce2cb63db3cd0e9bf88b3f9379d83d0e00e800d791db9b95cc375a4958effc68135e7d84d4fb98e3", "(BLAKE2b) abc with 44 byte key hash does not match")
assert(blake2b("abc", 64, "8ad6d6166cdc8c2ffd5f25c5e7f957513b4a0e66")
	== "4bfd84ddcc52b007d929ce27feed940295fe37b8f6d28bd6ce101fd53a95bed870df7e2d88a4ac50bd7772d1b245af3d663cb6bdb6a8b1fa5eda24a859dc433d", "(BLAKE2b) abc with 40 byte key hash does not match")
assert(blake2b("abc", 64, "b2b77f50103450a6aaed6b3b080dbe5bacde")
	== "4d396810a9f4f1448f35f9de94610fc5067b44a3544fa746af327ccae319e7ea05b9f6d4705e1d668639db9ba636541ee582484528b6eb5310f8beff797a9e8a", "(BLAKE2b) abc with 36 byte key hash does not match")
assert(blake2b("abc", 64, "bddd813c634239723171ef3fee98579b")
	== "a9ac1196ebf670f73f4764fd2d820c5c40818423a6f333fff98a6bb159aa24b20da24f44929c66c401c8a1d06d5d0f4af97633f276fabeddde3ed810c2385039", "(BLAKE2b) abc with 32 byte key hash does not match")
assert(blake2b("abc", 64, "cf4ab791c62b8d2b")
	== "e1ca64e8abc97d6b08fade1e242f7c39340d74d4a9b0d3317ef9d2fbbf28a25a05fe83f4a7470e0220db8d1818f62029f57281d2aa608ebad9154a865ac2da70", "(BLAKE2b) abc with 16 byte key hash does not match")
assert(blake2b("abc", 64, "d8bb14d8")
	== "bde1a9f80f3c88258053723754fff9d5155a5694a34669f7892c6469c0b9db6019b56b97ec25fa6b82173318fc27b67caee576cacd21e61f312bb6ea3c49c414", "(BLAKE2b) abc with 8 byte key hash does not match")
assert(blake2b("abc", 64, "6390")
	== "bef63af934a11a46a647ca0496bd03844cd52206bcf6eb95e8d1e815f7ca30415461cbeb269bfd09b8652518253fe00bb614e854a3f55900486799b474807736", "(BLAKE2b) abc with 4 byte key hash does not match")
assert(blake2b("abc", 64, "ae")
	== "15cf17d6b18fcc83f2b4235492df9b98176ae9dba2de5b7ce29c8eb47b921fbbca7bd60ef779c437361a404090d30589626bf9d6edddf84027a7eef217c30bc5", "(BLAKE2b) abc with 2 byte key hash does not match")
assert(blake2b("abc", 64, "6")
	== "968ff3c3b73ad7825542c57d18f755ea5879894cf4ebd7a0720972ec627e563334f05c132cd62f3373b003564d7b87831f9fa306ae86b37ae9e8fe5977cc4836", "(BLAKE2b) abc with 1 byte key hash does not match")
--stylua: ignore end

assert(
	blake2b("", 64)
		== "786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce",
	"(BLAKE2b) empty hash does not match"
)
assert(
	blake2b("", 64, "786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419")
		== "0532e878558d08b3586bb2771609976c0d7d521e23447086aff388ff65b2d643b11b180f894ded8fad97a1bf2c4ac48caf2234d85699e44a5c40187dea2c21f5",
	"(BLAKE2b) empty hash with key does not match"
)

assert(
	blake2b("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", 64)
		== "7285ff3e8bd768d69be62b3bf18765a325917fa9744ac2f582a20850bc2b1141ed1b3e4528595acc90772bdf2d37dc8a47130b44f33a02e8730e5ad8e166e888",
	"(BLAKE2b) 448 bit alphabet hash does not match"
)
assert(
	blake2b(
		"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
		64,
		"7285ff3e8bd768d69be62b3bf18765a325917fa9744ac2f582a20850bc2b1141"
	)
		== "e1988e2c9f4888f90e0d299a1f1ea651802c441edda1444931bad0e3f5cd0ad6840259504c54bf4bee74400ded9f7b9d824cdbbb354722b609cd8423c4e004de",
	"(BLAKE2b) 448 bit alphabet hash with key does not match"
)

assert(
	blake2b(
		"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
		64
	)
		== "ce741ac5930fe346811175c5227bb7bfcd47f42612fae46c0809514f9e0e3a11ee1773287147cdeaeedff50709aa716341fe65240f4ad6777d6bfaf9726e5e52",
	"(BLAKE2b) 896 bit alphabet hash does not match"
)
assert(
	blake2b(
		"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
		64,
		"ce741ac5930fe346811175c5227bb7bfcd47f42612fae46c0809514f9e0e3a11"
	)
		== "d64e6645fc551fcb490f6cdb97fc70679c02f38ede8ef9be5a65d2ea5fbd0e61581aada01a1af6986070659e3a4c7343f069d510aaf8246b30dd3290c8e03831",
	"(BLAKE2b) 896 bit alphabet hash with key does not match"
)

assert(
	blake2b("foo", 64)
		== "ca002330e69d3e6b84a46a56a6533fd79d51d97a3bb7cad6c2ff43b354185d6dc1e723fb3db4ae0737e120378424c714bb982d9dc5bbd7a0ab318240ddd18f8d",
	"(BLAKE2b) foo hash does not match"
)
assert(
	blake2b("foo", 64, "ca002330e69d3e6b84a46a56a6533fd79d51d97a3bb7cad6c2ff43b354185d6d")
		== "1863442c12970f0f698383d9c8b3864e852c01cb49dff1a4e4a7f0aea90ed73177caf460b7396aee9739c2d991e8666b232965633c2a78b548f3f7535de8d3b0",
	"(BLAKE2b) foo hash with key does not match"
)

assert(
	blake2b("bar", 64)
		== "76aafe37ce69887569c3c1a51f14b639191fb2180cb0c87b566529496636712868556a9adf069d59769bf7e2393d215f195d8e7694f26fc7e20d92195973add8",
	"(BLAKE2b) bar hash does not match"
)
assert(
	blake2b("bar", 64, "76aafe37ce69887569c3c1a51f14b639191fb2180cb0c87b5665294966367128")
		== "aee2742827f9eeb2270fa21a8b630c59633855886791b43021d70c6fd5c8d9c590a339c629c2653c6eb8959de89a4bdac2fbde208434a1d0b6c0f363c856927e",
	"(BLAKE2b) bar hash with key does not match"
)

assert(
	blake2b("baz", 64)
		== "2305476f21a28dd31ba7aaa4bcbd92780ff6c3ee77d45ea025dfec737e6bc725ce391585326dc22208f77c2643ca4afa34334042858a6f250e9094c8f77c82f6",
	"(BLAKE2b) baz hash does not match"
)
assert(
	blake2b("baz", 64, "2305476f21a28dd31ba7aaa4bcbd92780ff6c3ee77d45ea025dfec737e6bc725")
		== "cf757dd4a0cfec70bf13dcd160852845560b9b3e8ce8020348825dd0f0e29716a081d5703983646957008ac2f1f6fd043a9c47bfbb9480176eeddc6cb6ee1e4f",
	"(BLAKE2b) baz hash with key does not match"
)

assert(
	blake2b("The Fitness-Gram Pacer Test is a multi-stage aerobic capacity test", 64)
		== "7a17312276da3e41be9ef397aa035eadffe426afa70856d11155395f5af77932021e7db7995c536a86fbf9e840763f177d7bb2287c726a70b75494b018b6700c",
	"(BLAKE2b) Fitness-Gram hash does not match"
)
assert(
	blake2b(
		"The Fitness-Gram Pacer Test is a multi-stage aerobic capacity test",
		64,
		"7a17312276da3e41be9ef397aa035eadffe426afa70856d11155395f5af77932"
	)
		== "d4ad49ad88892b37a5067739e07d3af18fece632f023dd266a2027668b6628424d21cca8daa6fbffca724bb3e623526995a3a808e2e01553eda36e3ae2f74a8f",
	"(BLAKE2b) Fitness-Gram hash with key does not match"
)

assert(
	blake2b(string.rep("!", 128), 64)
		== "cc28dceb6925b2e02a177a961a0f819f464e3fa78c34be6117ea8ac6fbe02b58e6c91a1faf5605d3be43b23414a99c0322ada77a088f3454f980be1d3df68813",
	"(BLAKE2b) block size hash does not match"
)

assert(
	blake2b(string.rep("!", 128), 64, "cc28dceb6925b2e02a177a961a0f819f464e3fa78c34be6117ea8ac6fbe02b58")
		== "a58b0e2f33b9fac979f77a58e30031a0ae5ec6c8946180bb1e03aaacbed8e94585f26c62925d1877e341076cbd5ae0ece4ac114009af2b6cf0f9e3671626eae4",
	"(BLAKE2b) block size hash with key does not match"
)

if true then
	local e = string.rep("e", 199999)
	local a = string.rep("a", 1e6)
	assert(
		blake2b(e, 64)
			== "720d7925523d2ca4e39e11e91231890446abaffd09f5e69628b326e5ad0295a4c0335726da08fc56120e52f378042a8146e2e56f38187f33bd23e8ec42b88f35",
		"(BLAKE2b) e hash does not match"
	)
	assert(
		blake2b(e, 64, "720d7925523d2ca4e39e11e91231890446abaffd09f5e69628b326e5ad0295a4")
			== "9ac0a89038f6d3a82b681a8ad902e9c4e520b2597c7478184639084ce4ba5ab05c16f40859269a185837b208888cf870b4e2aa0d60e8a6cb0b1e2643b69d5526",
		"(BLAKE2b) e hash with key does not match"
	)

	assert(
		blake2b(a, 64)
			== "98fb3efb7206fd19ebf69b6f312cf7b64e3b94dbe1a17107913975a793f177e1d077609d7fba363cbba00d05f7aa4e4fa8715d6428104c0a75643b0ff3fd3eaf",
		"(BLAKE2b) million a hash does not match"
	)

	assert(
		blake2b(a, 64, "98fb3efb7206fd19ebf69b6f312cf7b64e3b94dbe1a17107913975a793f177e1")
			== "a01e874c2a19010ac02b16ea0eba0c03c434c00aeccb45fbe05b658111eff7dd254800b26c65819e420e4173e8731b4274a6561e3c7888db2d34cc488e315480",
		"(BLAKE2b) million a hash with key does not match"
	)
end

print("Change in memory:", gcinfo() - startgc)
print("BLAKE2b tests completed. Took", os.clock() - t)


local blake2s = require("../modules/blake2s")

local t = os.clock()
local startgc = gcinfo()

assert(
	blake2s("abc", 32) == "508c5e8c327c14e2e1a72ba34eeb452f37458b209ed63a294d999b4c86675982",
	"(BLAKE2s) abc hash does not match"
)
assert(
	blake2s("abc", 32, "508c5e8c327c14e2e1a72ba34eeb452f")
		== "1e3ed5414ca157217cdf3d99bbdcdcd852f3239426d1fae9fcbc9c8224381a52",
	"(BLAKE2s) abc hash with key does not match"
)

assert(
	blake2s("abc", 24) == "1e2ed10fcdbc46e0ab3ea3f268a6c288083ae04e3d63a8de",
	"(BLAKE2s) 24 byte abc hash does not match"
)
assert(blake2s("abc", 16) == "aa4938119b1dc7b87cbad0ffd200d0ae", "(BLAKE2s) 16 byte abc hash does not match")
assert(blake2s("abc", 8) == "972e9d2cd6de6402", "(BLAKE2s) 8 byte abc hash does not match")
assert(blake2s("abc", 4) == "df7101d3", "(BLAKE2s) 4 byte abc hash does not match")
assert(blake2s("abc", 2) == "d8ce", "(BLAKE2s) 2 byte abc hash does not match")
assert(blake2s("abc", 1) == "0d", "(BLAKE2s) 1 byte abc hash does not match")

assert(
	blake2s("abc", 32, "972e9d2cd6de6402") == "4fa189d3cbc9d46f2ddf4d191ec7df1b4e7692fd86ac59baad9ec4142ac29aaa",
	"(BLAKE2s) abc hash with 16 byte key does not match"
)
assert(
	blake2s("abc", 32, "df7101d3") == "edc95d536a0c0b9246b7049da3f4c93d1107b408b979b907e0335a5c587b0098",
	"(BLAKE2s) abc hash with 8 byte key does not match"
)
assert(
	blake2s("abc", 32, "d8ce") == "414cd323f567172cbadcd550f44c6bc55401464890fa0558948669dda8001c0d",
	"(BLAKE2s) abc hash with 4 byte key does not match"
)
assert(
	blake2s("abc", 32, "0d") == "1320e01ea2e1a43455a1151b9b0b66b9e0080863548de404baf81e7b0683a1b1",
	"(BLAKE2s) abc hash with 2 byte key does not match"
)
assert(
	blake2s("abc", 32, "!") == "7eb68f58048d5338c25ed1bfb7fc1da7c2d081e085ee614166cbf04bc39097b8",
	"(BLAKE2s) abc hash with 1 byte key does not match"
)

assert(
	blake2s("", 32) == "69217a3079908094e11121d042354a7c1f55b6482ca1a51e1b250dfd1ed0eef9",
	"(BLAKE2s) empty hash does not match"
)
assert(
	blake2s("", 32, "69217a3079908094e11121d042354a7c")
		== "8406a898942510f828424cd7d4f72f41a64d9cda64ba05fd48a3f0dd829e1da0",
	"(BLAKE2s) empty hash with key does not match"
)

assert(
	blake2s("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", 32)
		== "6f4df5116a6f332edab1d9e10ee87df6557beab6259d7663f3bcd5722c13f189",
	"(BLAKE2s) 448 bit alphabet hash does not match"
)
assert(
	blake2s("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", 32, "6f4df5116a6f332edab1d9e10ee87df6")
		== "c45765e894725e81476fc92a1d9581a297e6ff8841512cc29f0a8057e0bf66d8",
	"(BLAKE2s) 448 bit alphabet hash with key does not match"
)

assert(
	blake2s(
		"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
		32
	) == "358dd2ed0780d4054e76cb6f3a5bce2841e8e2f547431d4d09db21b66d941fc7",
	"(BLAKE2s) 896 bit alphabet hash does not match"
)
assert(
	blake2s(
		"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
		32,
		"358dd2ed0780d4054e76cb6f3a5bce28"
	) == "de16ae3d2b7a991ce0dc8b159be527690e710bc8875161b997d9b0d5784ab0cf",
	"(BLAKE2s) 896 bit alphabet hash with key does not match"
)

assert(
	blake2s("foo", 32) == "08d6cad88075de8f192db097573d0e829411cd91eb6ec65e8fc16c017edfdb74",
	"(BLAKE2s) foo hash does not match"
)
assert(
	blake2s("foo", 32, "08d6cad88075de8f192db097573d0e82")
		== "069b70e4116be3b0e97d387cdda4f1d70a578e95245044b681cfe73e906b8735",
	"(BLAKE2s) foo hash with key does not match"
)

assert(
	blake2s("bar", 32) == "3d9f08067d69177f67c827ac12b23e06c23939813ec80fe347e2a9c2d29c9efd",
	"(BLAKE2s) bar hash does not match"
)
assert(
	blake2s("bar", 32, "3d9f08067d69177f67c827ac12b23e06")
		== "05bb25ef9904619c35082954770bb1930cd3d89e6d583b3d3d9bd2883a5d5b3c",
	"(BLAKE2s) bar hash with key does not match"
)

assert(
	blake2s("baz", 32) == "72e45eca20335d5aaf5d1315be9c894f13b72a7cfb08333c2db0446416917a85",
	"(BLAKE2s) baz hash does not match"
)
assert(
	blake2s("baz", 32, "72e45eca20335d5aaf5d1315be9c894f")
		== "6b67a68821709d1fe804225e0a921a069bdc3839ce32359fd46e82289d2987f9",
	"(BLAKE2s) baz hash with key does not match"
)

assert(
	blake2s("The Fitness-Gram Pacer Test is a multi-stage aerobic capacity test", 32)
		== "30c54a159664099dd262c2654dd7516a9af58b5a39e7e007cc98170f91ebe11b",
	"(BLAKE2s) Fitness-Gram hash does not match"
)
assert(
	blake2s(
		"The Fitness-Gram Pacer Test is a multi-stage aerobic capacity test",
		32,
		"30c54a159664099dd262c2654dd7516a"
	) == "3f9000a9abeae3e051df3df67158a31f040ec2f558853c11f1a18694bea4b9f6",
	"(BLAKE2s) Fitness-Gram hash with key does not match"
)

assert(
	blake2s(string.rep("!", 64), 32) == "c6eb166cd5e940868e35cbc55297913eef2975fee2d83be813a6ae66e15d5afc",
	"(BLAKE2s) block size hash does not match"
)
assert(
	blake2s(string.rep("!", 64), 32, "c6eb166cd5e940868e35cbc55297913e")
		== "141298c1c32245af1a0be43a1673830abb5e386f5dfb24c8fb9c37234782d392",
	"(BLAKE2s) block size hash with key does not match"
)

if true then
	local e = string.rep("e", 199999)
	local a = string.rep("a", 1e6)
	assert(
		blake2s(e, 32) == "c95a33f8b1fbc9d5c529fd791eb646e203dca4b648feecfebb1faf140bd14812",
		"(BLAKE2s) e hash does not match"
	)
	assert(
		blake2s(e, 32, "c95a33f8b1fbc9d5c529fd791eb646e2")
			== "aabcabcf10521774a73a11532649c23f8da9f05c5054e7cf7070efc23a670175",
		"(BLAKE2s) e hash with key does not match"
	)

	assert(
		blake2s(a, 32) == "bec0c0e6cde5b67acb73b81f79a67a4079ae1c60dac9d2661af18e9f8b50dfa5",
		"(BLAKE2s) million a hash does not match"
	)

	assert(
		blake2s(a, 32, "bec0c0e6cde5b67acb73b81f79a67a40")
			== "43ddf787fa4a73ab85039e5fe4d3a234a6282bf084f531c8fb39dfb4fed5cf70",
		"(BLAKE2s) million a hash with key does not match"
	)
end

print("Change in memory:", gcinfo() - startgc)
print("BLAKE2s tests completed. Took", os.clock() - t)


local md5 = require("../modules/md5")

local t = os.clock()
local startgc = gcinfo()

local hash, digest = md5("abc")
assert(hash == "900150983cd24fb0d6963f7d28e17f72", "(MD5) abc hash does not match")
assert(digest[1] == 0x90015098, "(MD5) first word of abc hash was incorrect")
assert(digest[2] == 0x3cd24fb0, "(MD5) second word of abc hash was incorrect")
assert(digest[3] == 0xd6963f7d, "(MD5) third word of abc hash was incorrect")
assert(digest[4] == 0x28e17f72, "(MD5) fourth word of abc hash was incorrect")
assert(table.isfrozen(digest), "(MD5) returned digest of abc hash was not frozen")

assert(md5("") == "d41d8cd98f00b204e9800998ecf8427e", "(MD5) empty hash does not match")
assert(
	md5("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") == "8215ef0796a20bcaaae116d3876c664a",
	"(MD5) 448 bit alphabet hash does not match"
)
assert(
	md5(
		"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"
	) == "03dd8807a93175fb062dfb55dc7d359c",
	"(MD5) 896 bit alphabet hash does not match"
)
assert(md5("foo") == "acbd18db4cc2f85cedef654fccc4a4d8", "(MD5) foo hash does not match")
assert(md5("bar") == "37b51d194a7513e45b56f6524f2d51f2", "(MD5) bar hash does not match")
assert(md5("baz") == "73feffa4b7f6bb68e44cf984c85f6e88", "(MD5) baz hash does not match")
assert(
	md5("The Fitness-Gram Pacer Test is a multi-stage aerobic capacity test") == "eaed1e8132c94f0fc9ae8fe181b2cd2c",
	"(MD5) Fitness-Gram hash does not match"
)

assert(
	md5("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") == "ef1772b6dff9a122358552954ad0df65",
	"(MD5) 55 character a hash does not match"
)

assert(
	md5(
		"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
	) == "8a7bd0732ed6a28ce75f6dabc90e1613",
	"(MD5) 119 character a hash does not match"
)

assert(
	md5(
		"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
	) == "089f243d1e831c5879aa375ee364a06e",
	"(MD5) 111 character a hash does not match"
)

assert(
	md5(
		"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
	) == "b7b40d64ffccebd78abcf522376b3aae",
	"(MD5) 239 character a hash does not match"
)

if true then
	local e = string.rep("e", 199999)
	local a = string.rep("a", 1e6)

	-- local t2 = os.clock()
	assert(md5(e) == "40015f794f5a3748aea71502c750033b", "(MD5) e hash does not match")
	-- print(`e hash took {os.clock() - t2}`)

	-- t2 = os.clock()
	assert(md5(a) == "7707d6ae4e027c70eea2a935c2296f21", "(MD5) million a hash does not match")
	-- print(`million a hash took {os.clock() - t2}`)
end

print("Change in memory:", gcinfo() - startgc)
print("MD5 tests completed. Took", os.clock() - t)


local sha1 = require("../modules/sha1")

local t = os.clock()
local startgc = gcinfo()

local hash, digest = sha1("abc")
assert(hash == "a9993e364706816aba3e25717850c26c9cd0d89d", "(SHA-1) abc hash does not match")
assert(digest[1] == 0xa9993e36, "(SHA-1) first word of abc hash was incorrect")
assert(digest[2] == 0x4706816a, "(SHA-1) second word of abc hash was incorrect")
assert(digest[3] == 0xba3e2571, "(SHA-1) third word of abc hash was incorrect")
assert(digest[4] == 0x7850c26c, "(SHA-1) fourth word of abc hash was incorrect")
assert(digest[5] == 0x9cd0d89d, "(SHA-1) fifth word of abc hash was incorrect")
assert(table.isfrozen(digest), "(SHA-1) returned digest of abc hash was not frozen")

assert(sha1("") == "da39a3ee5e6b4b0d3255bfef95601890afd80709", "(SHA-1) empty hash does not match")
assert(
	sha1("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") == "84983e441c3bd26ebaae4aa1f95129e5e54670f1",
	"(SHA-1) 448 bit alphabet hash does not match"
)
assert(
	sha1(
		"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"
	) == "a49b2446a02c645bf419f995b67091253a04a259",
	"(SHA-1) 896 bit alphabet hash does not match"
)
assert(sha1("foo") == "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33", "(SHA-1) foo hash does not match")
assert(sha1("bar") == "62cdb7020ff920e5aa642c3d4066950dd1f01f4d", "(SHA-1) bar hash does not match")
assert(sha1("baz") == "bbe960a25ea311d21d40669e93df2003ba9b90a2", "(SHA-1) baz hash does not match")
assert(
	sha1("The Fitness-Gram Pacer Test is a multi-stage aerobic capacity test")
		== "fe32af74bc982dc5da23e54055f5515e948a10bd",
	"(SHA-1) Fitness-Gram hash does not match"
)

assert(
	sha1("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") == "c1c8bbdc22796e28c0e15163d20899b65621d65a",
	"(SHA-1) 55 character a hash does not match"
)

assert(
	sha1(
		"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
	) == "ee971065aaa017e0632a8ca6c77bb3bf8b1dfc56",
	"(SHA-1) 119 character a hash does not match"
)

assert(
	sha1(
		"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
	) == "ac877859d427d9192054eea8feb3b8a403ef83a5",
	"(SHA-1) 111 character a hash does not match"
)

assert(
	sha1(
		"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
	) == "fdc30857cf7b957f47ebd8288d5e5d7426f44394",
	"(SHA-1) 239 character a hash does not match"
)

if true then
	local e = string.rep("e", 199999)
	local a = string.rep("a", 1e6)

	-- local t2 = os.clock()
	assert(sha1(e) == "07fe6fab7549089cb7b256545b1f31fe7ed74207", "(SHA-1) e hash does not match")
	-- print(`e hash took {os.clock() - t2}`)

	-- t2 = os.clock()
	assert(sha1(a) == "34aa973cd4c4daa4f61eeb2bdbad27316534016f", "(SHA-1) million a hash does not match")
	-- print(`million a hash took {os.clock() - t2}`)
end

print("Change in memory:", gcinfo() - startgc)
print("SHA-1 tests completed. Took", os.clock() - t)


local sha224 = require("../modules/sha224")

local t = os.clock()
local startgc = gcinfo()

local hash, digest = sha224("abc")
assert(hash == "23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7", "(SHA-224) abc hash does not match")
assert(digest[1] == 0x23097d22, "(SHA-224) first word of abc hash was incorrect")
assert(digest[2] == 0x3405d822, "(SHA-224) second word of abc hash was incorrect")
assert(digest[3] == 0x8642a477, "(SHA-224) third word of abc hash was incorrect")
assert(digest[4] == 0xbda255b3, "(SHA-224) fourth word of abc hash was incorrect")
assert(digest[5] == 0x2aadbce4, "(SHA-224) fifth word of abc hash was incorrect")
assert(digest[6] == 0xbda0b3f7, "(SHA-224) sixth word of abc hash was incorrect")
assert(digest[7] == 0xe36c9da7, "(SHA-224) seventh word of abc hash was incorrect")
assert(digest[8] == nil, "(SHA-224) abc hash was not properly truncated")
assert(table.isfrozen(digest), "(SHA-224) returned digest of abc hash was not frozen")

assert(sha224("") == "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f", "(SHA-224) empty hash does not match")
assert(
	sha224("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq")
		== "75388b16512776cc5dba5da1fd890150b0c6455cb4f58b1952522525",
	"(SHA-224) 448 bit alphabet hash does not match"
)
assert(
	sha224(
		"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"
	) == "c97ca9a559850ce97a04a96def6d99a9e0e0e2ab14e6b8df265fc0b3",
	"(SHA-224) 896 bit alphabet hash does not match"
)
assert(sha224("foo") == "0808f64e60d58979fcb676c96ec938270dea42445aeefcd3a4e6f8db", "(SHA-224) foo hash does not match")
assert(sha224("bar") == "07daf010de7f7f0d8d76a76eb8d1eb40182c8d1e7a3877a6686c9bf0", "(SHA-224) bar hash does not match")
assert(sha224("baz") == "1846d1bd30922b6492a1a28bc940fd00efcd2d9bfb00e34e94bf8048", "(SHA-224) baz hash does not match")
assert(
	sha224("The Fitness-Gram Pacer Test is a multi-stage aerobic capacity test")
		== "795f46c7974e93ca25ba2abf56525c5a234563b43b774904bc25fd27",
	"(SHA-224) Fitness-Gram hash does not match"
)

assert(
	sha224("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
		== "fb0bd626a70c28541dfa781bb5cc4d7d7f56622a58f01a0b1ddd646f",
	"(SHA-224) 55 character a hash does not match"
)

assert(
	sha224(
		"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
	) == "e000e6709d26667b631faa7fc1bd404eb4774003c5fb4f51a0184875",
	"(SHA-224) 119 character a hash does not match"
)

assert(
	sha224(
		"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
	) == "4aeec1a49b2c1bc663abf2809b36faaa64359523d4f26d02dbc2cba3",
	"(SHA-224) 111 character a hash does not match"
)

assert(
	sha224(
		"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
	) == "9da4e535cdffdbb7ee783ef7c6b61cbda7bcd4b15ce59d6ce5c2f099",
	"(SHA-224) 239 character a hash does not match"
)

if true then
	local e = string.rep("e", 199999)
	local a = string.rep("a", 1e6)
	assert(sha224(e) == "386146cd6a85d13e8d66b8131bbb6c61e08d84a514ae845c59c13fd0", "(SHA-224) e hash does not match")
	assert(
		sha224(a) == "20794655980c91d8bbb4c1ea97618a4bf03f42581948b2ee4ee7ad67",
		"(SHA-224) million a hash does not match"
	)
end

print("Change in memory:", gcinfo() - startgc)
print("SHA-224 tests completed. Took", os.clock() - t)


local sha256 = require("../modules/sha256")

local t = os.clock()
local startgc = gcinfo()

local hash, digest = sha256("abc")
assert(hash == "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", "(SHA-256) abc hash does not match")
assert(digest[1] == 0xba7816bf, "(SHA-256) first word of abc hash was incorrect")
assert(digest[2] == 0x8f01cfea, "(SHA-256) second word of abc hash was incorrect")
assert(digest[3] == 0x414140de, "(SHA-256) third word of abc hash was incorrect")
assert(digest[4] == 0x5dae2223, "(SHA-256) fourth word of abc hash was incorrect")
assert(digest[5] == 0xb00361a3, "(SHA-256) fifth word of abc hash was incorrect")
assert(digest[6] == 0x96177a9c, "(SHA-256) sixth word of abc hash was incorrect")
assert(digest[7] == 0xb410ff61, "(SHA-256) seventh word of abc hash was incorrect")
assert(digest[8] == 0xf20015ad, "(SHA-256) eighth word of abc hash was incorrect")
assert(table.isfrozen(digest), "(SHA-256) returned digest of abc hash was not frozen")

assert(
	sha256("") == "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
	"(SHA-256) empty hash does not match"
)
assert(
	sha256("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq")
		== "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1",
	"(SHA-256) 448 bit alphabet hash does not match"
)
assert(
	sha256(
		"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"
	) == "cf5b16a778af8380036ce59e7b0492370b249b11e8f07a51afac45037afee9d1",
	"(SHA-256) 896 bit alphabet hash does not match"
)
assert(
	sha256("foo") == "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
	"(SHA-256) foo hash does not match"
)
assert(
	sha256("bar") == "fcde2b2edba56bf408601fb721fe9b5c338d10ee429ea04fae5511b68fbf8fb9",
	"(SHA-256) bar hash does not match"
)
assert(
	sha256("baz") == "baa5a0964d3320fbc0c6a922140453c8513ea24ab8fd0577034804a967248096",
	"(SHA-256) baz hash does not match"
)
assert(
	sha256("The Fitness-Gram Pacer Test is a multi-stage aerobic capacity test")
		== "8509e3a2478a4b7fb712420a8bc4177c2271de6122b0d8bcb792c2ba4203a330",
	"(SHA-256) Fitness-Gram hash does not match"
)

assert(
	sha256("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
		== "9f4390f8d30c2dd92ec9f095b65e2b9ae9b0a925a5258e241c9f1e910f734318",
	"(SHA-256) 55 character a hash does not match"
)

assert(
	sha256(
		"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
	) == "31eba51c313a5c08226adf18d4a359cfdfd8d2e816b13f4af952f7ea6584dcfb",
	"(SHA-256) 119 character a hash does not match"
)

assert(
	sha256(
		"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
	) == "6374f73208854473827f6f6a3f43b1f53eaa3b82c21c1a6d69a2110b2a79baad",
	"(SHA-256) 111 character a hash does not match"
)

assert(
	sha256(
		"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
	) == "064b3d122abe25c36265f79fc794b0adf28a6c5e4fe8ed3661f2287e8cecadcc",
	"(SHA-256) 239 character a hash does not match"
)

if true then
	local e = string.rep("e", 199999)
	local a = string.rep("a", 1e6)
	assert(
		sha256(e) == "434cf81dca15a72777e811ed4ae9144f9272ca3c04ff9c2de1533bbbffed5449",
		"(SHA-256) e hash does not match"
	)
	assert(
		sha256(a) == "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0",
		"(SHA-256) million a hash does not match"
	)
end

print("Change in memory:", gcinfo() - startgc)
print("SHA-256 tests completed. Took", os.clock() - t)


local sha384 = require("../modules/sha384")

local t = os.clock()
local startgc = gcinfo()

local hash, digest = sha384("abc")
assert(
	hash == "cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7",
	"(SHA-384) abc hash does not match"
)
assert(digest[1] == 0xcb00753f, "(SHA-384) front half of first word of abc hash was incorrect")
assert(digest[2] == 0x45a35e8b, "(SHA-384) back half of first word hash was incorrect")
assert(digest[3] == 0xb5a03d69, "(SHA-384) front half of second word of abc hash was incorrect")
assert(digest[4] == 0x9ac65007, "(SHA-384) back half of second word of abc hash was incorrect")
assert(digest[5] == 0x272c32ab, "(SHA-384) front half of third word of abc hash was incorrect")
assert(digest[6] == 0x0eded163, "(SHA-384) back half of third word of abc hash was incorrect")
assert(digest[7] == 0x1a8b605a, "(SHA-384) front half of fourth word of abc hash was incorrect")
assert(digest[8] == 0x43ff5bed, "(SHA-384) back half of fourth word of abc hash was incorrect")
assert(digest[9] == 0x8086072b, "(SHA-384) front half of fifth word of abc hash was incorrect")
assert(digest[10] == 0xa1e7cc23, "(SHA-384) back half of fifth word of abc hash was incorrect")
assert(digest[11] == 0x58baeca1, "(SHA-384) front half of sixth word of abc hash was incorrect")
assert(digest[12] == 0x34c825a7, "(SHA-384) back half of sixth word of abc hash was incorrect")
assert(digest[13] == nil, "(SHA-384) abc hash was not properly truncated")
assert(digest[14] == nil, "(SHA-384) abc hash was not properly truncated")
assert(digest[15] == nil, "(SHA-384) abc hash was not properly truncated")
assert(digest[16] == nil, "(SHA-384) abc hash was not properly truncated")
assert(table.isfrozen(digest), "(SHA-384) returned digest of abc hash was not frozen")

assert(
	sha384("") == "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b",
	"(SHA-384) empty hash does not match"
)
assert(
	sha384("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq")
		== "3391fdddfc8dc7393707a65b1b4709397cf8b1d162af05abfe8f450de5f36bc6b0455a8520bc4e6f5fe95b1fe3c8452b",
	"(SHA-384) 448 bit alphabet hash does not match"
)
assert(
	sha384(
		"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"
	) == "09330c33f71147e83d192fc782cd1b4753111b173b3b05d22fa08086e3b0f712fcc7c71a557e2db966c3e9fa91746039",
	"(SHA-384) 896 bit alphabet hash does not match"
)
assert(
	sha384("foo") == "98c11ffdfdd540676b1a137cb1a22b2a70350c9a44171d6b1180c6be5cbb2ee3f79d532c8a1dd9ef2e8e08e752a3babb",
	"(SHA-384) foo hash does not match"
)
assert(
	sha384("bar") == "14919aaff0da5efeb871fe8a438061c1996e88bfe199e2796b3b5c5c65714f61183adc53d48c3a32734ca6faf7d7fda8",
	"(SHA-384) bar hash does not match"
)
assert(
	sha384("baz") == "967004d25de4abc1bd6a7c9a216254a5ac0733e8ad96dc9f1ea0fad9619da7c32d654ec8ad8ba2f9b5728fed6633bd91",
	"(SHA-384) baz hash does not match"
)
assert(
	sha384("The Fitness-Gram Pacer Test is a multi-stage aerobic capacity test")
		== "7f72ed4ea5a1300eee1e9afdbca7e91cca65daf5f1f183cdc28b11e376c22503ee22d833c8c32f41624567f4517c02dd",
	"(SHA-384) Fitness-Gram hash does not match"
)

assert(
	sha384("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
		== "5d91ac7e74e62b5c728904b40f10784d66b7af9cb6302123e48c92f0432ceb8d2a92c02de77dcb29ed75c4b42bde46f4",
	"(SHA-384) 55 character a hash does not match"
)

assert(
	sha384(
		"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
	) == "c2fbb1911d6889e3db556b482236ab82f3c736f00a22c088641a09fdbbca27e3f1e3b6235bad20aee1ca083c76ac590c",
	"(SHA-384) 119 character a hash does not match"
)

assert(
	sha384(
		"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
	) == "3c37955051cb5c3026f94d551d5b5e2ac38d572ae4e07172085fed81f8466b8f90dc23a8ffcdea0b8d8e58e8fdacc80a",
	"(SHA-384) 111 character a hash does not match"
)

assert(
	sha384(
		"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
	) == "e247c35f4bc1aa38026f8880c8c97305545d00d3f859e00c57d1c1f0a176b3c6b749c4eb081f08bd0fba500969cd056a",
	"(SHA-384) 239 character a hash does not match"
)

if true then
	local e = string.rep("e", 199999)
	local a = string.rep("a", 1e6)
	assert(
		sha384(e) == "69aa2eaa3de72baf7133d2554fe503bc325b63272461460f617a822541ebdc48c04422fe9b15b5d60795692b3126e61f",
		"(SHA-384) e hash does not match"
	)
	assert(
		sha384(a) == "9d0e1809716474cb086e834e310a4a1ced149e9c00f248527972cec5704c2a5b07b8b3dc38ecc4ebae97ddd87f3d8985",
		"(SHA-384) million a hash does not match"
	)
end

print("Change in memory:", gcinfo() - startgc)
print("SHA-384 tests completed. Took", os.clock() - t)