--[=[
	Cryptography library: Blake3

	Sizes:
		Key: 32 bytes
		Output: variable

	Return type: string (hex)
	Example usage:
		local Message = buffer.fromstring("Hello World")
		local Key = buffer.fromstring(string.rep("k", 32))

		--------Standard Hash--------
		local Hash = Blake3.Digest(Message, 32)

		--------Keyed Hash--------
		local KeyedHash = Blake3.DigestKeyed(Message, Key, 32)

		--------Key Derivation--------
		local Context = buffer.fromstring("my context")
		local KeyDeriver = Blake3.DeriveKey(Context)
		local DerivedKey = KeyDeriver(Message, 32)
--]=]

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

local Blake3 = {}

local BLOCK_SIZE = 64
local CV_SIZE = 32
local EXTENDED_CV_SIZE = 64
local MAX_STACK_DEPTH = 64
local STACK_BUFFER_SIZE = MAX_STACK_DEPTH * CV_SIZE

local BLAKE3_KEY_SIZE = 32
local BLAKE3_MIN_OUTPUT_BYTES = 1
local BLAKE3_MAX_OUTPUT_BYTES = 2^32 - 1

local CHUNK_START = 0x01
local CHUNK_END = 0x02
local PARENT_FLAG = 0x04
local ROOT_FLAG = 0x08
local HASH_FLAG = 0x10
local DERIVE_CONTEXT_FLAG = 0x20
local DERIVE_MATERIAL_FLAG = 0x40

local INITIAL_VECTORS = buffer.create(CV_SIZE) do
	local IV = {
		0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
		0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
	}
	for Index, Value in ipairs(IV) do
		buffer.writeu32(INITIAL_VECTORS, (Index - 1) * 4, Value)
	end
end

local ENCODE_LOOKUP = buffer.create(256 * 2) do
	local HexChars = "0123456789abcdef"
	for Byte = 0, 255 do
		local HighNibble = bit32.rshift(Byte, 4)
		local LowNibble = Byte % 16

		local HighChar = string.byte(HexChars, HighNibble + 1)
		local LowChar = string.byte(HexChars, LowNibble + 1)

		local Combined = HighChar + bit32.lshift(LowChar, 8)
		buffer.writeu16(ENCODE_LOOKUP, Byte * 2, Combined)
	end
end

local function Compress(Hash: buffer, MessageBlock: buffer, Counter: number, V14: number, V15: number, IsFull: boolean?): buffer
	local Hash00 = buffer.readu32(Hash, 0)
	local Hash01 = buffer.readu32(Hash, 4)
	local Hash02 = buffer.readu32(Hash, 8)
	local Hash03 = buffer.readu32(Hash, 12)
	local Hash04 = buffer.readu32(Hash, 16)
	local Hash05 = buffer.readu32(Hash, 20)
	local Hash06 = buffer.readu32(Hash, 24)
	local Hash07 = buffer.readu32(Hash, 28)

	local V00, V01, V02, V03 = Hash00, Hash01, Hash02, Hash03
	local V04, V05, V06, V07 = Hash04, Hash05, Hash06, Hash07
	local V08, V09, V10, V11 = 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a

	local V12 = Counter % (2 ^ 32)
	local V13 = (Counter - V12) * (2 ^ -32)

	local M00 = buffer.readu32(MessageBlock, 0)
	local M01 = buffer.readu32(MessageBlock, 4)
	local M02 = buffer.readu32(MessageBlock, 8)
	local M03 = buffer.readu32(MessageBlock, 12)
	local M04 = buffer.readu32(MessageBlock, 16)
	local M05 = buffer.readu32(MessageBlock, 20)
	local M06 = buffer.readu32(MessageBlock, 24)
	local M07 = buffer.readu32(MessageBlock, 28)
	local M08 = buffer.readu32(MessageBlock, 32)
	local M09 = buffer.readu32(MessageBlock, 36)
	local M10 = buffer.readu32(MessageBlock, 40)
	local M11 = buffer.readu32(MessageBlock, 44)
	local M12 = buffer.readu32(MessageBlock, 48)
	local M13 = buffer.readu32(MessageBlock, 52)
	local M14 = buffer.readu32(MessageBlock, 56)
	local M15 = buffer.readu32(MessageBlock, 60)

	local Temp
	for Index = 1, 7 do
		V00 += V04 + M00; V12 = bit32.lrotate(bit32.bxor(V12, V00), 16)
		V08 += V12; V04 = bit32.lrotate(bit32.bxor(V04, V08), 20)
		V00 += V04 + M01; V12 = bit32.lrotate(bit32.bxor(V12, V00), 24)
		V08 += V12; V04 = bit32.lrotate(bit32.bxor(V04, V08), 25)

		V01 += V05 + M02; V13 = bit32.lrotate(bit32.bxor(V13, V01), 16)
		V09 += V13; V05 = bit32.lrotate(bit32.bxor(V05, V09), 20)
		V01 += V05 + M03; V13 = bit32.lrotate(bit32.bxor(V13, V01), 24)
		V09 += V13; V05 = bit32.lrotate(bit32.bxor(V05, V09), 25)

		V02 += V06 + M04; V14 = bit32.lrotate(bit32.bxor(V14, V02), 16)
		V10 += V14; V06 = bit32.lrotate(bit32.bxor(V06, V10), 20)
		V02 += V06 + M05; V14 = bit32.lrotate(bit32.bxor(V14, V02), 24)
		V10 += V14; V06 = bit32.lrotate(bit32.bxor(V06, V10), 25)

		V03 += V07 + M06; V15 = bit32.lrotate(bit32.bxor(V15, V03), 16)
		V11 += V15; V07 = bit32.lrotate(bit32.bxor(V07, V11), 20)
		V03 += V07 + M07; V15 = bit32.lrotate(bit32.bxor(V15, V03), 24)
		V11 += V15; V07 = bit32.lrotate(bit32.bxor(V07, V11), 25)

		V00 += V05 + M08; V15 = bit32.lrotate(bit32.bxor(V15, V00), 16)
		V10 += V15; V05 = bit32.lrotate(bit32.bxor(V05, V10), 20)
		V00 += V05 + M09; V15 = bit32.lrotate(bit32.bxor(V15, V00), 24)
		V10 += V15; V05 = bit32.lrotate(bit32.bxor(V05, V10), 25)

		V01 += V06 + M10; V12 = bit32.lrotate(bit32.bxor(V12, V01), 16)
		V11 += V12; V06 = bit32.lrotate(bit32.bxor(V06, V11), 20)
		V01 += V06 + M11; V12 = bit32.lrotate(bit32.bxor(V12, V01), 24)
		V11 += V12; V06 = bit32.lrotate(bit32.bxor(V06, V11), 25)

		V02 += V07 + M12; V13 = bit32.lrotate(bit32.bxor(V13, V02), 16)
		V08 += V13; V07 = bit32.lrotate(bit32.bxor(V07, V08), 20)
		V02 += V07 + M13; V13 = bit32.lrotate(bit32.bxor(V13, V02), 24)
		V08 += V13; V07 = bit32.lrotate(bit32.bxor(V07, V08), 25)

		V03 += V04 + M14; V14 = bit32.lrotate(bit32.bxor(V14, V03), 16)
		V09 += V14; V04 = bit32.lrotate(bit32.bxor(V04, V09), 20)
		V03 += V04 + M15; V14 = bit32.lrotate(bit32.bxor(V14, V03), 24)
		V09 += V14; V04 = bit32.lrotate(bit32.bxor(V04, V09), 25)

		if Index ~= 7 then
			Temp = M02
			M02 = M03
			M03 = M10
			M10 = M12
			M12 = M09
			M09 = M11
			M11 = M05
			M05 = M00
			M00 = Temp

			Temp = M06
			M06 = M04
			M04 = M07
			M07 = M13
			M13 = M14
			M14 = M15
			M15 = M08
			M08 = M01
			M01 = Temp
		end
	end

	if IsFull then
		local Result = buffer.create(EXTENDED_CV_SIZE)
		buffer.writeu32(Result, 0, bit32.bxor(V00, V08))
		buffer.writeu32(Result, 4, bit32.bxor(V01, V09))
		buffer.writeu32(Result, 8, bit32.bxor(V02, V10))
		buffer.writeu32(Result, 12, bit32.bxor(V03, V11))
		buffer.writeu32(Result, 16, bit32.bxor(V04, V12))
		buffer.writeu32(Result, 20, bit32.bxor(V05, V13))
		buffer.writeu32(Result, 24, bit32.bxor(V06, V14))
		buffer.writeu32(Result, 28, bit32.bxor(V07, V15))

		buffer.writeu32(Result, 32, bit32.bxor(V08, Hash00))
		buffer.writeu32(Result, 36, bit32.bxor(V09, Hash01))
		buffer.writeu32(Result, 40, bit32.bxor(V10, Hash02))
		buffer.writeu32(Result, 44, bit32.bxor(V11, Hash03))
		buffer.writeu32(Result, 48, bit32.bxor(V12, Hash04))
		buffer.writeu32(Result, 52, bit32.bxor(V13, Hash05))
		buffer.writeu32(Result, 56, bit32.bxor(V14, Hash06))
		buffer.writeu32(Result, 60, bit32.bxor(V15, Hash07))

		return Result
	else
		local Result = buffer.create(CV_SIZE)
		buffer.writeu32(Result, 0, bit32.bxor(V00, V08))
		buffer.writeu32(Result, 4, bit32.bxor(V01, V09))
		buffer.writeu32(Result, 8, bit32.bxor(V02, V10))
		buffer.writeu32(Result, 12, bit32.bxor(V03, V11))
		buffer.writeu32(Result, 16, bit32.bxor(V04, V12))
		buffer.writeu32(Result, 20, bit32.bxor(V05, V13))
		buffer.writeu32(Result, 24, bit32.bxor(V06, V14))
		buffer.writeu32(Result, 28, bit32.bxor(V07, V15))

		return Result
	end
end

local function ProcessMessage(InitialHashVector: buffer, Flags: number, Message: buffer, Length: number): buffer
	local MessageLength = buffer.len(Message)
	local StateCvs = buffer.create(STACK_BUFFER_SIZE)
	local StateCv = buffer.create(CV_SIZE)

	local StackSize = 0
	local StateCounter = 0

	local StateChunkNumber = 0
	local StateEndFlag = 0

	local BlockSize = BLOCK_SIZE
	local CvSize = CV_SIZE
	local ExtendedCvSize = EXTENDED_CV_SIZE

	local StateStartFlag = CHUNK_START
	local ChunkStart = CHUNK_START
	local ChunkEnd = CHUNK_END

	local ParentFlag = PARENT_FLAG
	local RootFlag = ROOT_FLAG
	local FlagsParent = Flags + ParentFlag

	local BlockBuffer = buffer.create(BlockSize)
	local PopCv = buffer.create(CvSize)
	local MergeBlock = buffer.create(ExtendedCvSize)
	local StackCv = buffer.create(CvSize)
	local Block = buffer.create(ExtendedCvSize)

	buffer.copy(StateCv, 0, InitialHashVector, 0, CvSize)

	for BlockOffset = 0, MessageLength - BlockSize - 1, BlockSize do
		buffer.copy(BlockBuffer, 0, Message, BlockOffset, BlockSize)
		local StateFlags = Flags + StateStartFlag + StateEndFlag
		StateCv = Compress(StateCv, BlockBuffer, StateCounter, BlockSize, StateFlags)
		StateStartFlag = 0
		StateChunkNumber += 1

		if StateChunkNumber == 15 then
			StateEndFlag = ChunkEnd
		elseif StateChunkNumber == 16 then
			local MergeCv = StateCv
			local MergeAmount = StateCounter + 1

			while MergeAmount % 2 == 0 do
				StackSize -= 1
				buffer.copy(PopCv, 0, StateCvs, StackSize * CvSize, CvSize)
				buffer.copy(MergeBlock, 0, PopCv, 0, CvSize)
				buffer.copy(MergeBlock, CvSize, MergeCv, 0, CvSize)
				MergeCv = Compress(InitialHashVector, MergeBlock, 0, BlockSize, FlagsParent)
				MergeAmount /= 2
			end

			buffer.copy(StateCvs, StackSize * CvSize, MergeCv, 0, CvSize)
			StackSize += 1

			buffer.copy(StateCv, 0, InitialHashVector, 0, CvSize)
			StateStartFlag = ChunkStart
			StateCounter += 1
			StateChunkNumber = 0
			StateEndFlag = 0
		end
	end

	local LastLength = MessageLength == 0 and 0 or ((MessageLength - 1) % BlockSize + 1)
	local PaddedMessage = buffer.create(BlockSize)
	if LastLength > 0 then
		buffer.copy(PaddedMessage, 0, Message, MessageLength - LastLength, LastLength)
	end

	local OutputCv: buffer
	local OutputBlock: buffer
	local OutputLength: number
	local OutputFlags: number

	if StateCounter > 0 then
		local StateFlags = Flags + StateStartFlag + ChunkEnd
		local MergeCv = Compress(StateCv, PaddedMessage, StateCounter, LastLength, StateFlags)
		for Index = StackSize, 2, -1 do
			buffer.copy(StackCv, 0, StateCvs, (Index - 1) * CvSize, CvSize)
			buffer.copy(Block, 0, StackCv, 0, CvSize)
			buffer.copy(Block, CvSize, MergeCv, 0, CvSize)
			MergeCv = Compress(InitialHashVector, Block, 0, BlockSize, FlagsParent)
		end

		OutputCv = InitialHashVector
		local FirstStackCv = buffer.create(CvSize)
		buffer.copy(FirstStackCv, 0, StateCvs, 0, CvSize)
		OutputBlock = buffer.create(ExtendedCvSize)

		buffer.copy(OutputBlock, 0, FirstStackCv, 0, CvSize)
		buffer.copy(OutputBlock, CvSize, MergeCv, 0, CvSize)

		OutputLength = BlockSize
		OutputFlags = Flags + RootFlag + ParentFlag
	else
		OutputCv = StateCv
		OutputBlock = PaddedMessage
		OutputLength = LastLength
		OutputFlags = Flags + StateStartFlag + ChunkEnd + RootFlag
	end

	local Output = buffer.create(Length)
	local OutputOffset = 0
	for Index = 0, Length // BlockSize do
		local MessageDigest = Compress(OutputCv, OutputBlock, Index, OutputLength, OutputFlags, true)
		local BytesToCopy = math.min(BlockSize, Length - OutputOffset)
		buffer.copy(Output, OutputOffset, MessageDigest, 0, BytesToCopy)
		OutputOffset += BytesToCopy
		if OutputOffset >= Length then
			break
		end
	end

	return Output
end

local function ToHex(Buffer: buffer): string
	local Length = buffer.len(Buffer)
	local Hex = buffer.create(Length * 2)

	local Lookup = ENCODE_LOOKUP

	local Leftover = Length % 8
	local HexCursor = 0

	for Index = 0, Length - Leftover - 1, 8 do
		local Hex1 = buffer.readu16(Lookup, buffer.readu8(Buffer, Index) * 2)
		local Hex2 = buffer.readu16(Lookup, buffer.readu8(Buffer, Index + 1) * 2)
		local Hex3 = buffer.readu16(Lookup, buffer.readu8(Buffer, Index + 2) * 2)
		local Hex4 = buffer.readu16(Lookup, buffer.readu8(Buffer, Index + 3) * 2)
		local Hex5 = buffer.readu16(Lookup, buffer.readu8(Buffer, Index + 4) * 2)
		local Hex6 = buffer.readu16(Lookup, buffer.readu8(Buffer, Index + 5) * 2)
		local Hex7 = buffer.readu16(Lookup, buffer.readu8(Buffer, Index + 6) * 2)
		local Hex8 = buffer.readu16(Lookup, buffer.readu8(Buffer, Index + 7) * 2)

		buffer.writeu16(Hex, HexCursor, Hex1)
		buffer.writeu16(Hex, HexCursor + 2, Hex2)
		buffer.writeu16(Hex, HexCursor + 4, Hex3)
		buffer.writeu16(Hex, HexCursor + 6, Hex4)
		buffer.writeu16(Hex, HexCursor + 8, Hex5)
		buffer.writeu16(Hex, HexCursor + 10, Hex6)
		buffer.writeu16(Hex, HexCursor + 12, Hex7)
		buffer.writeu16(Hex, HexCursor + 14, Hex8)

		HexCursor += 16
	end

	for Index = Length - Leftover, Length - 1 do
		local HexPair = buffer.readu16(Lookup, buffer.readu8(Buffer, Index) * 2)
		buffer.writeu16(Hex, HexCursor, HexPair)
		HexCursor += 2
	end

	return buffer.tostring(Hex)
end

function Blake3.Digest(Message: buffer, Length: number?): (string, buffer)
	if Message == nil then
		error("Message cannot be nil", 2)
	end

	if typeof(Message) ~= "buffer" then
		error(`Message must be a buffer, got {typeof(Message)}`, 2)
	end

	if Length then
		if typeof(Length) ~= "number" then
			error(`Length must be a number, got {typeof(Length)}`, 2)
		end

		if Length ~= math.floor(Length) then
			error(`Length must be an integer, got {Length}`, 2)
		end

		if Length < BLAKE3_MIN_OUTPUT_BYTES then
			error(`Length must be at least {BLAKE3_MIN_OUTPUT_BYTES} byte, got {Length} bytes`, 2)
		end

		if Length > BLAKE3_MAX_OUTPUT_BYTES then
			error(`Length must be at most {BLAKE3_MAX_OUTPUT_BYTES} bytes, got {Length} bytes`, 2)
		end
	end

	local Result = ProcessMessage(INITIAL_VECTORS, 0, Message, Length or 32)

	return ToHex(Result), Result
end

function Blake3.DigestKeyed(Message: buffer, Key: buffer, Length: number?): (string, buffer)
	if Key == nil then
		error("Key cannot be nil", 2)
	end

	if typeof(Key) ~= "buffer" then
		error(`Key must be a buffer, got {typeof(Key)}`, 2)
	end

	local KeyLength = buffer.len(Key)
	if KeyLength ~= BLAKE3_KEY_SIZE then
		error(`Key must be exactly {BLAKE3_KEY_SIZE} bytes long, got {KeyLength} bytes`, 2)
	end

	if Message == nil then
		error("Message cannot be nil", 2)
	end
	if typeof(Message) ~= "buffer" then
		error(`Message must be a buffer, got {typeof(Message)}`, 2)
	end

	if Length then
		if typeof(Length) ~= "number" then
			error(`Length must be a number, got {typeof(Length)}`, 2)
		end
		if Length ~= math.floor(Length) then
			error(`Length must be an integer, got {Length}`, 2)
		end
		if Length < BLAKE3_MIN_OUTPUT_BYTES then
			error(`Length must be at least {BLAKE3_MIN_OUTPUT_BYTES} byte, got {Length} bytes`, 2)
		end
		if Length > BLAKE3_MAX_OUTPUT_BYTES then
			error(`Length must be at most {BLAKE3_MAX_OUTPUT_BYTES} bytes, got {Length} bytes`, 2)
		end
	end

	local Result = ProcessMessage(Key, HASH_FLAG, Message, Length or 32)

	return ToHex(Result), Result
end

function Blake3.DeriveKey(Context: buffer): (buffer, number?) -> (string, buffer)
	if Context == nil then
		error("Context cannot be nil", 2)
	end

	if typeof(Context) ~= "buffer" then
		error(`Context must be a buffer, got {typeof(Context)}`, 2)
	end

	local ContextHash = ProcessMessage(INITIAL_VECTORS, DERIVE_CONTEXT_FLAG, Context, 32)

	return function(Material: buffer, Length: number?): (string, buffer)
		if Material == nil then
			error("Material cannot be nil", 2)
		end

		if typeof(Material) ~= "buffer" then
			error(`Material must be a buffer, got {typeof(Material)}`, 2)
		end

		if Length then
			if typeof(Length) ~= "number" then
				error(`Length must be a number, got {typeof(Length)}`, 2)
			end

			if Length ~= math.floor(Length) then
				error(`Length must be an integer, got {Length}`, 2)
			end

			if Length < BLAKE3_MIN_OUTPUT_BYTES then
				error(`Length must be at least {BLAKE3_MIN_OUTPUT_BYTES} byte, got {Length} bytes`, 2)
			end

			if Length > BLAKE3_MAX_OUTPUT_BYTES then
				error(`Length must be at most {BLAKE3_MAX_OUTPUT_BYTES} bytes, got {Length} bytes`, 2)
			end
		end

		local Result = ProcessMessage(ContextHash, DERIVE_MATERIAL_FLAG, Material, Length or 32)

		return ToHex(Result), Result
	end
end

return Blake3


--[=[
	Cryptography library: HMAC

	Return type: string or function
	Example usage:
		local Message = buffer.fromstring("Hello World")
		local Key = buffer.fromstring("Key")
		local BlockSize = 64 -- SHA256 block length (= string.len(SHA256(...)))

		local Result = HMAC(Message, Key, SHA2.SHA256, BlockSize) -- SHA3/Blake family should have BigEndian = false
--]=]

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

export type HashFunction = (...any) -> (string, buffer)

local function FromByteAndSize(Byte: number, Size: number)
	local Buffer = buffer.create(Size)
	buffer.fill(Buffer, 0, Byte)
	return Buffer
end

local function ToBigEndian(Buffer: buffer)
	for Index = 0, buffer.len(Buffer) - 1, 4 do
		buffer.writeu32(Buffer, Index, bit32.byteswap(buffer.readu32(Buffer, Index)))
	end
end

local function ConcatenateBuffers(LeftBuffer: buffer, RightBuffer: buffer)
	local LeftBufLen = buffer.len(LeftBuffer)
	local Buffer = buffer.create(LeftBufLen + buffer.len(RightBuffer))

	buffer.copy(Buffer, 0, LeftBuffer)
	buffer.copy(Buffer, LeftBufLen, RightBuffer)

	return Buffer
end

local function XORBuffer(LeftBuffer: buffer, RightBuffer: buffer)
	local Size = math.min(buffer.len(LeftBuffer), buffer.len(RightBuffer))
	local NewBuffer = buffer.create(Size)

	for Index = 0, Size - 1 do
		local LeftValue = buffer.readu8(LeftBuffer, Index)
		local RightValue = buffer.readu8(RightBuffer, Index)
		buffer.writeu8(NewBuffer, Index, bit32.bxor(LeftValue, RightValue))
	end

	return NewBuffer
end

local function ComputeBlockSizedKey(Key: buffer, HashFunction: HashFunction, BlockSizeBytes: number, BigEndian: boolean?): buffer
	local KeyLength = buffer.len(Key)
	if KeyLength > BlockSizeBytes then
		local _, Digest = HashFunction(Key)
		if BigEndian ~= false then
			ToBigEndian(Digest)
		end

		local PaddedKey = buffer.create(BlockSizeBytes)
		buffer.copy(PaddedKey, 0, Digest)
		return PaddedKey
	elseif KeyLength < BlockSizeBytes then
		local PaddedKey = buffer.create(BlockSizeBytes)
		buffer.copy(PaddedKey, 0, Key)
		return PaddedKey
	end

	return Key
end

local function HMAC(Message: buffer, Key: buffer, HashFunction: HashFunction, BlockSizeBytes: number, BigEndian: boolean?): (string, buffer)
	local BlockSizedKey = ComputeBlockSizedKey(Key, HashFunction, BlockSizeBytes, BigEndian)
	local OuterPaddedKey = XORBuffer(BlockSizedKey, FromByteAndSize(0x5C, BlockSizeBytes))
	local InnerPaddedKey = XORBuffer(BlockSizedKey, FromByteAndSize(0x36, BlockSizeBytes))
	local _, HashedMessageWithInnerKey = HashFunction(ConcatenateBuffers(InnerPaddedKey, Message))

	if BigEndian ~= false then
		ToBigEndian(HashedMessageWithInnerKey)
	end

	local FinalMessage = ConcatenateBuffers(OuterPaddedKey, HashedMessageWithInnerKey)
	return HashFunction(FinalMessage)
end

return HMAC


--[=[
	Cryptography library: HKDF (HMAC-based Key Derivation Function)

	RFC 5869 extract-then-expand key derivation built on HMAC.

	Return type: buffer
	Example usage:
		local IKM = buffer.fromstring("input key material")
		local OKM = HKDF(IKM, Salt, Info, 32, SHA2.SHA256, 64, 32)
--]=]

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

local HMAC = require("@self/HMAC")

type HashFunction = (...any) -> (string, buffer)

local function ToBigEndian(Buffer: buffer)
	for Index = 0, buffer.len(Buffer) - 1, 4 do
		buffer.writeu32(Buffer, Index, bit32.byteswap(buffer.readu32(Buffer, Index)))
	end
end

local function HKDF(IKM: buffer, Salt: buffer?, Info: buffer?, Length: number, HashFunction: HashFunction, BlockSize: number, HashLength: number, BigEndian: boolean?): buffer
	local ActualSalt = if Salt and buffer.len(Salt) > 0 then Salt else buffer.create(HashLength)
	local ActualInfo = Info or buffer.create(0)
	local InfoLength = buffer.len(ActualInfo)
	local NeedsByteSwap = BigEndian ~= false

	local Iterations = math.ceil(Length / HashLength)
	if Iterations > 255 then
		error(`HKDF output length {Length} exceeds maximum {255 * HashLength}`)
	end

	local _, PRK = HMAC(IKM, ActualSalt, HashFunction, BlockSize, BigEndian)
	if NeedsByteSwap then
		ToBigEndian(PRK)
	end

	local MaxInputSize = HashLength + InfoLength + 1
	local Input = buffer.create(MaxInputSize)

	local OKM = buffer.create(Length)
	local PreviousLength = 0

	for Index = 1, Iterations do
		local InputSize = PreviousLength + InfoLength + 1

		if InfoLength > 0 then
			buffer.copy(Input, PreviousLength, ActualInfo)
		end
		buffer.writeu8(Input, PreviousLength + InfoLength, Index)

		local InputSlice: buffer
		if InputSize == MaxInputSize then
			InputSlice = Input
		else
			InputSlice = buffer.create(InputSize)
			buffer.copy(InputSlice, 0, Input, 0, InputSize)
		end

		local _, Block = HMAC(InputSlice, PRK, HashFunction, BlockSize, BigEndian)
		if NeedsByteSwap then
			ToBigEndian(Block)
		end

		local Offset = (Index - 1) * HashLength
		buffer.copy(OKM, Offset, Block, 0, math.min(HashLength, Length - Offset))

		buffer.copy(Input, 0, Block)
		PreviousLength = HashLength
	end

	return OKM
end

return HKDF


--[=[
	Cryptography library: KMAC (Keccak Message Authentication Code)

	KMAC128 and KMAC256 are keyed hash functions based on cSHAKE128 and cSHAKE256.
	They give variable length output and strong authentication.

	Unlike SHAKE and cSHAKE, changing the output length generates a new,
	unrelated output. Both variants support up to 256 bits of security strength.

	Key Derivation Usage:
		For key derivation, use information related to the derived keying material
		as input data. Include identities and optionally a nonce.

	Return type: (string, buffer)
	Example usage:
		local Key = buffer.fromstring("my-secret-key")
		local Message = buffer.fromstring("Hello World")
		local Output = buffer.create(32)

		-- KMAC128 with 32-byte output
		local Hex, Digest = KMAC128(Message, Key, Output, buffer.create(0))

		-- KMAC256 with custom string
		local Hex2, Digest2 = KMAC256(Message, Key, Output, buffer.fromstring("super-code"))
--]=]

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

local KMAC = {}

local ENCODE_LOOKUP = buffer.create(256 * 2) do
	local HexChars = "0123456789abcdef"
	for Byte = 0, 255 do
		local HighNibble = bit32.rshift(Byte, 4)
		local LowNibble = Byte % 16

		local HighChar = string.byte(HexChars, HighNibble + 1)
		local LowChar = string.byte(HexChars, LowNibble + 1)

		local Combined = HighChar + bit32.lshift(LowChar, 8)
		buffer.writeu16(ENCODE_LOOKUP, Byte * 2, Combined)
	end
end

local LOW_ROUND, HIGH_ROUND = buffer.create(96), buffer.create(96) do
	local HighFactorKeccak = 0
	local ShiftRegister = 29
	local function GetNextBit(): number
		local Result = ShiftRegister % 2
		ShiftRegister = bit32.bxor((ShiftRegister - Result) // 2, 142 * Result)

		return Result
	end

	for Index = 0, 23 do
		local LowValue = 0
		local Multiplier: number

		for _ = 1, 6 do
			Multiplier = if Multiplier then Multiplier * Multiplier * 2 else 1
			LowValue += GetNextBit() * Multiplier
		end

		local HighValue = GetNextBit() * Multiplier
		buffer.writeu32(HIGH_ROUND, Index * 4, HighValue)
		buffer.writeu32(LOW_ROUND, Index * 4, LowValue + HighValue * HighFactorKeccak)
	end
end

local LANES_LOW = buffer.create(100)
local LANES_HIGH = buffer.create(100)
local FUNCTION_NAME = buffer.fromstring("KMAC")

local function Keccak(LanesLow: buffer, LanesHigh: buffer, InputBuffer: buffer, Offset: number, Size: number, BlockSizeInBytes: number): ()
	local QuadWordsQuantity = BlockSizeInBytes // 8
	local RCHigh, RCLow = HIGH_ROUND, LOW_ROUND

	for Position = Offset, Offset + Size - 1, BlockSizeInBytes do
		for Index = 0, (QuadWordsQuantity - 1) * 4, 4 do
			local BufferPos = Position + Index * 2

			buffer.writeu32(LanesLow, Index, bit32.bxor(
				buffer.readu32(LanesLow, Index),
				buffer.readu32(InputBuffer, BufferPos)
				))

			buffer.writeu32(LanesHigh, Index, bit32.bxor(
				buffer.readu32(LanesHigh, Index),
				buffer.readu32(InputBuffer, BufferPos + 4)
				))
		end

		local Lane01Low, Lane01High = buffer.readu32(LanesLow, 0), buffer.readu32(LanesHigh, 0)
		local Lane02Low, Lane02High = buffer.readu32(LanesLow, 4), buffer.readu32(LanesHigh, 4)
		local Lane03Low, Lane03High = buffer.readu32(LanesLow, 8), buffer.readu32(LanesHigh, 8)

		local Lane04Low, Lane04High = buffer.readu32(LanesLow, 12), buffer.readu32(LanesHigh, 12)
		local Lane05Low, Lane05High = buffer.readu32(LanesLow, 16), buffer.readu32(LanesHigh, 16)
		local Lane06Low, Lane06High = buffer.readu32(LanesLow, 20), buffer.readu32(LanesHigh, 20)

		local Lane07Low, Lane07High = buffer.readu32(LanesLow, 24), buffer.readu32(LanesHigh, 24)
		local Lane08Low, Lane08High = buffer.readu32(LanesLow, 28), buffer.readu32(LanesHigh, 28)
		local Lane09Low, Lane09High = buffer.readu32(LanesLow, 32), buffer.readu32(LanesHigh, 32)

		local Lane10Low, Lane10High = buffer.readu32(LanesLow, 36), buffer.readu32(LanesHigh, 36)
		local Lane11Low, Lane11High = buffer.readu32(LanesLow, 40), buffer.readu32(LanesHigh, 40)
		local Lane12Low, Lane12High = buffer.readu32(LanesLow, 44), buffer.readu32(LanesHigh, 44)

		local Lane13Low, Lane13High = buffer.readu32(LanesLow, 48), buffer.readu32(LanesHigh, 48)
		local Lane14Low, Lane14High = buffer.readu32(LanesLow, 52), buffer.readu32(LanesHigh, 52)
		local Lane15Low, Lane15High = buffer.readu32(LanesLow, 56), buffer.readu32(LanesHigh, 56)

		local Lane16Low, Lane16High = buffer.readu32(LanesLow, 60), buffer.readu32(LanesHigh, 60)
		local Lane17Low, Lane17High = buffer.readu32(LanesLow, 64), buffer.readu32(LanesHigh, 64)
		local Lane18Low, Lane18High = buffer.readu32(LanesLow, 68), buffer.readu32(LanesHigh, 68)

		local Lane19Low, Lane19High = buffer.readu32(LanesLow, 72), buffer.readu32(LanesHigh, 72)
		local Lane20Low, Lane20High = buffer.readu32(LanesLow, 76), buffer.readu32(LanesHigh, 76)
		local Lane21Low, Lane21High = buffer.readu32(LanesLow, 80), buffer.readu32(LanesHigh, 80)

		local Lane22Low, Lane22High = buffer.readu32(LanesLow, 84), buffer.readu32(LanesHigh, 84)
		local Lane23Low, Lane23High = buffer.readu32(LanesLow, 88), buffer.readu32(LanesHigh, 88)
		local Lane24Low, Lane24High = buffer.readu32(LanesLow, 92), buffer.readu32(LanesHigh, 92)

		local Lane25Low, Lane25High = buffer.readu32(LanesLow, 96), buffer.readu32(LanesHigh, 96)

		for RoundIndex = 0, 92, 4 do
			local Column1Low, Column1High = bit32.bxor(Lane01Low, Lane06Low, Lane11Low, Lane16Low, Lane21Low), bit32.bxor(Lane01High, Lane06High, Lane11High, Lane16High, Lane21High)
			local Column2Low, Column2High = bit32.bxor(Lane02Low, Lane07Low, Lane12Low, Lane17Low, Lane22Low), bit32.bxor(Lane02High, Lane07High, Lane12High, Lane17High, Lane22High)
			local Column3Low, Column3High = bit32.bxor(Lane03Low, Lane08Low, Lane13Low, Lane18Low, Lane23Low), bit32.bxor(Lane03High, Lane08High, Lane13High, Lane18High, Lane23High)
			local Column4Low, Column4High = bit32.bxor(Lane04Low, Lane09Low, Lane14Low, Lane19Low, Lane24Low), bit32.bxor(Lane04High, Lane09High, Lane14High, Lane19High, Lane24High)
			local Column5Low, Column5High = bit32.bxor(Lane05Low, Lane10Low, Lane15Low, Lane20Low, Lane25Low), bit32.bxor(Lane05High, Lane10High, Lane15High, Lane20High, Lane25High)

			local DeltaLow, DeltaHigh = bit32.bxor(Column1Low, Column3Low * 2 + Column3High // 2147483648), bit32.bxor(Column1High, Column3High * 2 + Column3Low // 2147483648)
			local Temp0Low, Temp0High = bit32.bxor(DeltaLow, Lane02Low), bit32.bxor(DeltaHigh, Lane02High)
			local Temp1Low, Temp1High = bit32.bxor(DeltaLow, Lane07Low), bit32.bxor(DeltaHigh, Lane07High)
			local Temp2Low, Temp2High = bit32.bxor(DeltaLow, Lane12Low), bit32.bxor(DeltaHigh, Lane12High)
			local Temp3Low, Temp3High = bit32.bxor(DeltaLow, Lane17Low), bit32.bxor(DeltaHigh, Lane17High)
			local Temp4Low, Temp4High = bit32.bxor(DeltaLow, Lane22Low), bit32.bxor(DeltaHigh, Lane22High)

			Lane02Low = Temp1Low // 1048576 + (Temp1High * 4096); Lane02High = Temp1High // 1048576 + (Temp1Low * 4096)
			Lane07Low = Temp3Low // 524288 + (Temp3High * 8192); Lane07High = Temp3High // 524288 + (Temp3Low * 8192)
			Lane12Low = Temp0Low * 2 + Temp0High // 2147483648; Lane12High = Temp0High * 2 + Temp0Low // 2147483648
			Lane17Low = Temp2Low * 1024 + Temp2High // 4194304; Lane17High = Temp2High * 1024 + Temp2Low // 4194304
			Lane22Low = Temp4Low * 4 + Temp4High // 1073741824; Lane22High = Temp4High * 4 + Temp4Low // 1073741824

			DeltaLow = bit32.bxor(Column2Low, Column4Low * 2 + Column4High // 2147483648); DeltaHigh = bit32.bxor(Column2High, Column4High * 2 + Column4Low // 2147483648)
			Temp0Low = bit32.bxor(DeltaLow, Lane03Low); Temp0High = bit32.bxor(DeltaHigh, Lane03High)
			Temp1Low = bit32.bxor(DeltaLow, Lane08Low); Temp1High = bit32.bxor(DeltaHigh, Lane08High)
			Temp2Low = bit32.bxor(DeltaLow, Lane13Low); Temp2High = bit32.bxor(DeltaHigh, Lane13High)
			Temp3Low = bit32.bxor(DeltaLow, Lane18Low); Temp3High = bit32.bxor(DeltaHigh, Lane18High)
			Temp4Low = bit32.bxor(DeltaLow, Lane23Low); Temp4High = bit32.bxor(DeltaHigh, Lane23High)

			Lane03Low = Temp2Low // 2097152 + (Temp2High * 2048); Lane03High = Temp2High // 2097152 + (Temp2Low * 2048)
			Lane08Low = Temp4Low // 8 + bit32.bor(Temp4High * 536870912, 0); Lane08High = Temp4High // 8 + bit32.bor(Temp4Low * 536870912, 0)
			Lane13Low = Temp1Low * 64 + Temp1High // 67108864; Lane13High = Temp1High * 64 + Temp1Low // 67108864
			Lane18Low = (Temp3Low * 32768) + Temp3High // 131072; Lane18High = (Temp3High * 32768) + Temp3Low // 131072
			Lane23Low = Temp0Low // 4 + bit32.bor(Temp0High * 1073741824, 0); Lane23High = Temp0High // 4 + bit32.bor(Temp0Low * 1073741824, 0)

			DeltaLow = bit32.bxor(Column3Low, Column5Low * 2 + Column5High // 2147483648); DeltaHigh = bit32.bxor(Column3High, Column5High * 2 + Column5Low // 2147483648)
			Temp0Low = bit32.bxor(DeltaLow, Lane04Low); Temp0High = bit32.bxor(DeltaHigh, Lane04High)
			Temp1Low = bit32.bxor(DeltaLow, Lane09Low); Temp1High = bit32.bxor(DeltaHigh, Lane09High)
			Temp2Low = bit32.bxor(DeltaLow, Lane14Low); Temp2High = bit32.bxor(DeltaHigh, Lane14High)
			Temp3Low = bit32.bxor(DeltaLow, Lane19Low); Temp3High = bit32.bxor(DeltaHigh, Lane19High)
			Temp4Low = bit32.bxor(DeltaLow, Lane24Low); Temp4High = bit32.bxor(DeltaHigh, Lane24High)

			Lane04Low = bit32.bor(Temp3Low * 2097152, 0) + Temp3High // 2048; Lane04High = bit32.bor(Temp3High * 2097152, 0) + Temp3Low // 2048
			Lane09Low = bit32.bor(Temp0Low * 268435456, 0) + Temp0High // 16; Lane09High = bit32.bor(Temp0High * 268435456, 0) + Temp0Low // 16
			Lane14Low = bit32.bor(Temp2Low * 33554432, 0) + Temp2High // 128; Lane14High = bit32.bor(Temp2High * 33554432, 0) + Temp2Low // 128
			Lane19Low = Temp4Low // 256 + bit32.bor(Temp4High * 16777216, 0); Lane19High = Temp4High // 256 + bit32.bor(Temp4Low * 16777216, 0)
			Lane24Low = Temp1Low // 512 + bit32.bor(Temp1High * 8388608, 0); Lane24High = Temp1High // 512 + bit32.bor(Temp1Low * 8388608, 0)
			DeltaLow = bit32.bxor(Column4Low, Column1Low * 2 + Column1High // 2147483648); DeltaHigh = bit32.bxor(Column4High, Column1High * 2 + Column1Low // 2147483648)

			Temp0Low = bit32.bxor(DeltaLow, Lane05Low); Temp0High = bit32.bxor(DeltaHigh, Lane05High)
			Temp1Low = bit32.bxor(DeltaLow, Lane10Low); Temp1High = bit32.bxor(DeltaHigh, Lane10High)
			Temp2Low = bit32.bxor(DeltaLow, Lane15Low); Temp2High = bit32.bxor(DeltaHigh, Lane15High)
			Temp3Low = bit32.bxor(DeltaLow, Lane20Low); Temp3High = bit32.bxor(DeltaHigh, Lane20High)
			Temp4Low = bit32.bxor(DeltaLow, Lane25Low); Temp4High = bit32.bxor(DeltaHigh, Lane25High)

			Lane05Low = (Temp4Low * 16384) + Temp4High // 262144; Lane05High = (Temp4High * 16384) + Temp4Low // 262144
			Lane10Low = bit32.bor(Temp1Low * 1048576, 0) + Temp1High // 4096; Lane10High = bit32.bor(Temp1High * 1048576, 0) + Temp1Low // 4096
			Lane15Low = Temp3Low * 256 + Temp3High // 16777216; Lane15High = Temp3High * 256 + Temp3Low // 16777216
			Lane20Low = bit32.bor(Temp0Low * 134217728, 0) + Temp0High // 32; Lane20High = bit32.bor(Temp0High * 134217728, 0) + Temp0Low // 32
			Lane25Low = Temp2Low // 33554432 + Temp2High * 128; Lane25High = Temp2High // 33554432 + Temp2Low * 128

			DeltaLow = bit32.bxor(Column5Low, Column2Low * 2 + Column2High // 2147483648); DeltaHigh = bit32.bxor(Column5High, Column2High * 2 + Column2Low // 2147483648)
			Temp1Low = bit32.bxor(DeltaLow, Lane06Low); Temp1High = bit32.bxor(DeltaHigh, Lane06High)
			Temp2Low = bit32.bxor(DeltaLow, Lane11Low); Temp2High = bit32.bxor(DeltaHigh, Lane11High)
			Temp3Low = bit32.bxor(DeltaLow, Lane16Low); Temp3High = bit32.bxor(DeltaHigh, Lane16High)
			Temp4Low = bit32.bxor(DeltaLow, Lane21Low); Temp4High = bit32.bxor(DeltaHigh, Lane21High)
			Lane06Low = Temp2Low * 8 + Temp2High // 536870912; Lane06High = Temp2High * 8 + Temp2Low // 536870912
			Lane11Low = (Temp4Low * 262144) + Temp4High // 16384; Lane11High = (Temp4High * 262144) + Temp4Low // 16384
			Lane16Low = Temp1Low // 268435456 + Temp1High * 16; Lane16High = Temp1High // 268435456 + Temp1Low * 16
			Lane21Low = Temp3Low // 8388608 + Temp3High * 512; Lane21High = Temp3High // 8388608 + Temp3Low * 512
			Lane01Low = bit32.bxor(DeltaLow, Lane01Low); Lane01High = bit32.bxor(DeltaHigh, Lane01High)

			Lane01Low, Lane02Low, Lane03Low, Lane04Low, Lane05Low = bit32.bxor(Lane01Low, bit32.band(-1 - Lane02Low, Lane03Low)), bit32.bxor(Lane02Low, bit32.band(-1 - Lane03Low, Lane04Low)), bit32.bxor(Lane03Low, bit32.band(-1 - Lane04Low, Lane05Low)), bit32.bxor(Lane04Low, bit32.band(-1 - Lane05Low, Lane01Low)), bit32.bxor(Lane05Low, bit32.band(-1 - Lane01Low, Lane02Low)) :: number
			Lane01High, Lane02High, Lane03High, Lane04High, Lane05High = bit32.bxor(Lane01High, bit32.band(-1 - Lane02High, Lane03High)), bit32.bxor(Lane02High, bit32.band(-1 - Lane03High, Lane04High)), bit32.bxor(Lane03High, bit32.band(-1 - Lane04High, Lane05High)), bit32.bxor(Lane04High, bit32.band(-1 - Lane05High, Lane01High)), bit32.bxor(Lane05High, bit32.band(-1 - Lane01High, Lane02High)) :: number
			Lane06Low, Lane07Low, Lane08Low, Lane09Low, Lane10Low = bit32.bxor(Lane09Low, bit32.band(-1 - Lane10Low, Lane06Low)), bit32.bxor(Lane10Low, bit32.band(-1 - Lane06Low, Lane07Low)), bit32.bxor(Lane06Low, bit32.band(-1 - Lane07Low, Lane08Low)), bit32.bxor(Lane07Low, bit32.band(-1 - Lane08Low, Lane09Low)), bit32.bxor(Lane08Low, bit32.band(-1 - Lane09Low, Lane10Low)) :: number
			Lane06High, Lane07High, Lane08High, Lane09High, Lane10High = bit32.bxor(Lane09High, bit32.band(-1 - Lane10High, Lane06High)), bit32.bxor(Lane10High, bit32.band(-1 - Lane06High, Lane07High)), bit32.bxor(Lane06High, bit32.band(-1 - Lane07High, Lane08High)), bit32.bxor(Lane07High, bit32.band(-1 - Lane08High, Lane09High)), bit32.bxor(Lane08High, bit32.band(-1 - Lane09High, Lane10High)) :: number
			Lane11Low, Lane12Low, Lane13Low, Lane14Low, Lane15Low = bit32.bxor(Lane12Low, bit32.band(-1 - Lane13Low, Lane14Low)), bit32.bxor(Lane13Low, bit32.band(-1 - Lane14Low, Lane15Low)), bit32.bxor(Lane14Low, bit32.band(-1 - Lane15Low, Lane11Low)), bit32.bxor(Lane15Low, bit32.band(-1 - Lane11Low, Lane12Low)), bit32.bxor(Lane11Low, bit32.band(-1 - Lane12Low, Lane13Low)) :: number
			Lane11High, Lane12High, Lane13High, Lane14High, Lane15High = bit32.bxor(Lane12High, bit32.band(-1 - Lane13High, Lane14High)), bit32.bxor(Lane13High, bit32.band(-1 - Lane14High, Lane15High)), bit32.bxor(Lane14High, bit32.band(-1 - Lane15High, Lane11High)), bit32.bxor(Lane15High, bit32.band(-1 - Lane11High, Lane12High)), bit32.bxor(Lane11High, bit32.band(-1 - Lane12High, Lane13High)) :: number
			Lane16Low, Lane17Low, Lane18Low, Lane19Low, Lane20Low = bit32.bxor(Lane20Low, bit32.band(-1 - Lane16Low, Lane17Low)), bit32.bxor(Lane16Low, bit32.band(-1 - Lane17Low, Lane18Low)), bit32.bxor(Lane17Low, bit32.band(-1 - Lane18Low, Lane19Low)), bit32.bxor(Lane18Low, bit32.band(-1 - Lane19Low, Lane20Low)), bit32.bxor(Lane19Low, bit32.band(-1 - Lane20Low, Lane16Low)) :: number
			Lane16High, Lane17High, Lane18High, Lane19High, Lane20High = bit32.bxor(Lane20High, bit32.band(-1 - Lane16High, Lane17High)), bit32.bxor(Lane16High, bit32.band(-1 - Lane17High, Lane18High)), bit32.bxor(Lane17High, bit32.band(-1 - Lane18High, Lane19High)), bit32.bxor(Lane18High, bit32.band(-1 - Lane19High, Lane20High)), bit32.bxor(Lane19High, bit32.band(-1 - Lane20High, Lane16High)) :: number
			Lane21Low, Lane22Low, Lane23Low, Lane24Low, Lane25Low = bit32.bxor(Lane23Low, bit32.band(-1 - Lane24Low, Lane25Low)), bit32.bxor(Lane24Low, bit32.band(-1 - Lane25Low, Lane21Low)), bit32.bxor(Lane25Low, bit32.band(-1 - Lane21Low, Lane22Low)), bit32.bxor(Lane21Low, bit32.band(-1 - Lane22Low, Lane23Low)), bit32.bxor(Lane22Low, bit32.band(-1 - Lane23Low, Lane24Low)) :: number
			Lane21High, Lane22High, Lane23High, Lane24High, Lane25High = bit32.bxor(Lane23High, bit32.band(-1 - Lane24High, Lane25High)), bit32.bxor(Lane24High, bit32.band(-1 - Lane25High, Lane21High)), bit32.bxor(Lane25High, bit32.band(-1 - Lane21High, Lane22High)), bit32.bxor(Lane21High, bit32.band(-1 - Lane22High, Lane23High)), bit32.bxor(Lane22High, bit32.band(-1 - Lane23High, Lane24High)) :: number

			Lane01Low = bit32.bxor(Lane01Low, buffer.readu32(RCLow, RoundIndex))
			Lane01High = bit32.bxor(Lane01High, buffer.readu32(RCHigh, RoundIndex))
		end

		buffer.writeu32(LanesLow, 0, Lane01Low); buffer.writeu32(LanesHigh, 0, Lane01High)
		buffer.writeu32(LanesLow, 4, Lane02Low); buffer.writeu32(LanesHigh, 4, Lane02High)
		buffer.writeu32(LanesLow, 8, Lane03Low); buffer.writeu32(LanesHigh, 8, Lane03High)
		buffer.writeu32(LanesLow, 12, Lane04Low); buffer.writeu32(LanesHigh, 12, Lane04High)
		buffer.writeu32(LanesLow, 16, Lane05Low); buffer.writeu32(LanesHigh, 16, Lane05High)
		buffer.writeu32(LanesLow, 20, Lane06Low); buffer.writeu32(LanesHigh, 20, Lane06High)
		buffer.writeu32(LanesLow, 24, Lane07Low); buffer.writeu32(LanesHigh, 24, Lane07High)
		buffer.writeu32(LanesLow, 28, Lane08Low); buffer.writeu32(LanesHigh, 28, Lane08High)
		buffer.writeu32(LanesLow, 32, Lane09Low); buffer.writeu32(LanesHigh, 32, Lane09High)
		buffer.writeu32(LanesLow, 36, Lane10Low); buffer.writeu32(LanesHigh, 36, Lane10High)
		buffer.writeu32(LanesLow, 40, Lane11Low); buffer.writeu32(LanesHigh, 40, Lane11High)
		buffer.writeu32(LanesLow, 44, Lane12Low); buffer.writeu32(LanesHigh, 44, Lane12High)
		buffer.writeu32(LanesLow, 48, Lane13Low); buffer.writeu32(LanesHigh, 48, Lane13High)
		buffer.writeu32(LanesLow, 52, Lane14Low); buffer.writeu32(LanesHigh, 52, Lane14High)
		buffer.writeu32(LanesLow, 56, Lane15Low); buffer.writeu32(LanesHigh, 56, Lane15High)
		buffer.writeu32(LanesLow, 60, Lane16Low); buffer.writeu32(LanesHigh, 60, Lane16High)
		buffer.writeu32(LanesLow, 64, Lane17Low); buffer.writeu32(LanesHigh, 64, Lane17High)
		buffer.writeu32(LanesLow, 68, Lane18Low); buffer.writeu32(LanesHigh, 68, Lane18High)
		buffer.writeu32(LanesLow, 72, Lane19Low); buffer.writeu32(LanesHigh, 72, Lane19High)
		buffer.writeu32(LanesLow, 76, Lane20Low); buffer.writeu32(LanesHigh, 76, Lane20High)
		buffer.writeu32(LanesLow, 80, Lane21Low); buffer.writeu32(LanesHigh, 80, Lane21High)
		buffer.writeu32(LanesLow, 84, Lane22Low); buffer.writeu32(LanesHigh, 84, Lane22High)
		buffer.writeu32(LanesLow, 88, Lane23Low); buffer.writeu32(LanesHigh, 88, Lane23High)
		buffer.writeu32(LanesLow, 92, Lane24Low); buffer.writeu32(LanesHigh, 92, Lane24High)
		buffer.writeu32(LanesLow, 96, Lane25Low); buffer.writeu32(LanesHigh, 96, Lane25High)
	end
end

local LEFT_ENCODE_RESULT = buffer.create(5)
local RIGHT_ENCODE_RESULT = buffer.create(5)

local function LeftEncode(Value: number): (buffer, number)
	local Result = LEFT_ENCODE_RESULT

	if Value <= 0xFF then
		buffer.writeu8(Result, 0, 1)
		buffer.writeu8(Result, 1, Value)
		return Result, 2
	end

	if Value <= 0xFFFF then
		buffer.writeu8(Result, 0, 2)
		buffer.writeu16(Result, 1, bit32.byteswap(bit32.lshift(Value, 16)))
		return Result, 3
	end

	local ByteLength = if Value > 0xFFFFFF then 4 else 3
	buffer.writeu8(Result, 0, ByteLength)
	buffer.writeu32(Result, 1, bit32.byteswap(bit32.lshift(Value, (4 - ByteLength) * 8)))
	return Result, ByteLength + 1
end

local function RightEncode(Value: number): (buffer, number)
	local Result = RIGHT_ENCODE_RESULT

	if Value <= 0xFF then
		buffer.writeu8(Result, 0, Value)
		buffer.writeu8(Result, 1, 1)
		return Result, 2
	end

	if Value <= 0xFFFF then
		buffer.writeu16(Result, 0, bit32.byteswap(bit32.lshift(Value, 16)))
		buffer.writeu8(Result, 2, 2)
		return Result, 3
	end

	local ByteLength = if Value > 0xFFFFFF then 4 else 3
	buffer.writeu32(Result, 0, bit32.byteswap(bit32.lshift(Value, (4 - ByteLength) * 8)))
	buffer.writeu8(Result, ByteLength, ByteLength)
	return Result, ByteLength + 1
end

local function EncodeString(Data: buffer): buffer
	local DataLength = buffer.len(Data)

	local LengthEncoding, LengthEncodingSize = LeftEncode(DataLength * 8)

	local Result = buffer.create(LengthEncodingSize + DataLength)

	buffer.copy(Result, 0, LengthEncoding, 0, LengthEncodingSize)
	buffer.copy(Result, LengthEncodingSize, Data, 0, DataLength)

	return Result
end

local function Bytepad(Data: buffer, Rate: number): buffer
	local DataLength = buffer.len(Data)

	local RateEncoding, RateEncodingSize = LeftEncode(Rate)

	local TotalPrePadLength = RateEncodingSize + DataLength
	local PadLength = Rate - (TotalPrePadLength % Rate)
	if PadLength == Rate then
		PadLength = 0
	end

	local Result = buffer.create(TotalPrePadLength + PadLength)
	buffer.copy(Result, 0, RateEncoding, 0, RateEncodingSize)
	buffer.copy(Result, RateEncodingSize, Data, 0, DataLength)

	return Result
end

local function CSHAKE(Output: buffer, CustomBuffer: buffer?, Data: buffer, RateBytes: number): ()
	buffer.fill(LANES_LOW, 0, 0, 100)
	buffer.fill(LANES_HIGH, 0, 0, 100)

	local LanesLow = LANES_LOW
	local LanesHigh = LANES_HIGH

	local OutputBytes = buffer.len(Output)

	local EncodedFunctionName = EncodeString(FUNCTION_NAME)
	local EncodedFunctionNameSize = buffer.len(EncodedFunctionName)

	local PrefixData: buffer
	if CustomBuffer then
		local EncodedCustomization = EncodeString(CustomBuffer)
		local EncodedCustomizationSize = buffer.len(EncodedCustomization)
		PrefixData = buffer.create(EncodedFunctionNameSize + EncodedCustomizationSize)
		buffer.copy(PrefixData, 0, EncodedFunctionName, 0, EncodedFunctionNameSize)
		buffer.copy(PrefixData, EncodedFunctionNameSize, EncodedCustomization, 0, EncodedCustomizationSize)
	else
		PrefixData = EncodedFunctionName
	end

	local BytepaddedPrefix = Bytepad(PrefixData, RateBytes)
	local BytepaddedPrefixSize = buffer.len(BytepaddedPrefix)
	local DataSize = buffer.len(Data)
	local TotalInputSize = BytepaddedPrefixSize + DataSize

	local PaddedLength = TotalInputSize + 1
	local Remainder = PaddedLength % RateBytes
	if Remainder ~= 0 then
		PaddedLength += (RateBytes - Remainder)
	end

	local PaddedMessage = buffer.create(PaddedLength)
	buffer.copy(PaddedMessage, 0, BytepaddedPrefix, 0, BytepaddedPrefixSize)
	buffer.copy(PaddedMessage, BytepaddedPrefixSize, Data, 0, DataSize)

	local DomainSeparator = 0x04
	if PaddedLength - TotalInputSize == 1 then
		buffer.writeu8(PaddedMessage, TotalInputSize, bit32.bor(DomainSeparator, 0x80))
	else
		buffer.writeu8(PaddedMessage, TotalInputSize, DomainSeparator)
		if PaddedLength - TotalInputSize > 2 then
			buffer.fill(PaddedMessage, TotalInputSize + 1, 0, PaddedLength - TotalInputSize - 2)
		end
		buffer.writeu8(PaddedMessage, PaddedLength - 1, 0x80)
	end

	Keccak(LanesLow, LanesHigh, PaddedMessage, 0, PaddedLength, RateBytes)

	local OutputOffset = 0
	local ZeroBuffer = buffer.create(RateBytes)

	while OutputOffset < OutputBytes do
		local BytesThisRound = math.min(RateBytes, OutputBytes - OutputOffset)

		for ByteIndex = 0, BytesThisRound - 1 do
			local AbsoluteIndex = OutputOffset + ByteIndex
			if AbsoluteIndex < OutputBytes then
				local Lane = ByteIndex // 8
				local ByteInLane = ByteIndex % 8
				local LaneOffset = Lane * 4

				local Value
				if ByteInLane < 4 then
					Value = bit32.extract(buffer.readu32(LanesLow, LaneOffset), ByteInLane * 8, 8)
				else
					Value = bit32.extract(buffer.readu32(LanesHigh, LaneOffset), (ByteInLane - 4) * 8, 8)
				end
				buffer.writeu8(Output, AbsoluteIndex, Value)
			end
		end

		OutputOffset += BytesThisRound

		if OutputOffset < OutputBytes then
			Keccak(LanesLow, LanesHigh, ZeroBuffer, 0, RateBytes, RateBytes)
		end
	end
end

function KMAC.KMAC128(Data: buffer, Key: buffer, Output: buffer, CustomBuffer: buffer?): (string, buffer)
	local OutputBytes = buffer.len(Output)

	local EncodedKey = EncodeString(Key)
	local BytepaddedKey = Bytepad(EncodedKey, 168)

	local BytepaddedKeySize = buffer.len(BytepaddedKey)
	local DataSize = buffer.len(Data)

	local RightEncodedLength, RightEncodedLengthSize = RightEncode(OutputBytes * 8)

	local Hex = buffer.create(OutputBytes * 2)
	local Lookup = ENCODE_LOOKUP

	local Leftover = OutputBytes % 8
	local HexCursor = 0

	local CombinedData = buffer.create(BytepaddedKeySize + DataSize + RightEncodedLengthSize)
	buffer.copy(CombinedData, 0, BytepaddedKey, 0, BytepaddedKeySize)
	buffer.copy(CombinedData, BytepaddedKeySize, Data, 0, DataSize)
	buffer.copy(CombinedData, BytepaddedKeySize + DataSize, RightEncodedLength, 0, RightEncodedLengthSize)

	CSHAKE(Output, CustomBuffer, CombinedData, 168)

	for Index = 0, OutputBytes - Leftover - 1, 8 do
		local Hex1 = buffer.readu16(Lookup, buffer.readu8(Output, Index) * 2)
		local Hex2 = buffer.readu16(Lookup, buffer.readu8(Output, Index + 1) * 2)
		local Hex3 = buffer.readu16(Lookup, buffer.readu8(Output, Index + 2) * 2)
		local Hex4 = buffer.readu16(Lookup, buffer.readu8(Output, Index + 3) * 2)
		local Hex5 = buffer.readu16(Lookup, buffer.readu8(Output, Index + 4) * 2)
		local Hex6 = buffer.readu16(Lookup, buffer.readu8(Output, Index + 5) * 2)
		local Hex7 = buffer.readu16(Lookup, buffer.readu8(Output, Index + 6) * 2)
		local Hex8 = buffer.readu16(Lookup, buffer.readu8(Output, Index + 7) * 2)

		buffer.writeu16(Hex, HexCursor, Hex1)
		buffer.writeu16(Hex, HexCursor + 2, Hex2)
		buffer.writeu16(Hex, HexCursor + 4, Hex3)
		buffer.writeu16(Hex, HexCursor + 6, Hex4)
		buffer.writeu16(Hex, HexCursor + 8, Hex5)
		buffer.writeu16(Hex, HexCursor + 10, Hex6)
		buffer.writeu16(Hex, HexCursor + 12, Hex7)
		buffer.writeu16(Hex, HexCursor + 14, Hex8)

		HexCursor += 16
	end

	for Index = OutputBytes - Leftover, OutputBytes - 1 do
		local HexPair = buffer.readu16(Lookup, buffer.readu8(Output, Index) * 2)
		buffer.writeu16(Hex, HexCursor, HexPair)
		HexCursor += 2
	end

	return buffer.tostring(Hex), Output
end

function KMAC.KMAC256(Data: buffer, Key: buffer, Output: buffer, CustomBuffer: buffer?): (string, buffer)
	local OutputBytes = buffer.len(Output)

	local EncodedKey = EncodeString(Key)
	local BytepaddedKey = Bytepad(EncodedKey, 136)

	local BytepaddedKeySize = buffer.len(BytepaddedKey)
	local DataSize = buffer.len(Data)

	local RightEncodedLength, RightEncodedLengthSize = RightEncode(OutputBytes * 8)

	local Hex = buffer.create(OutputBytes * 2)
	local Lookup = ENCODE_LOOKUP

	local Leftover = OutputBytes % 8
	local HexCursor = 0

	local CombinedData = buffer.create(BytepaddedKeySize + DataSize + RightEncodedLengthSize)
	buffer.copy(CombinedData, 0, BytepaddedKey, 0, BytepaddedKeySize)
	buffer.copy(CombinedData, BytepaddedKeySize, Data, 0, DataSize)
	buffer.copy(CombinedData, BytepaddedKeySize + DataSize, RightEncodedLength, 0, RightEncodedLengthSize)

	CSHAKE(Output, CustomBuffer, CombinedData, 136)

	for Index = 0, OutputBytes - Leftover - 1, 8 do
		local Hex1 = buffer.readu16(Lookup, buffer.readu8(Output, Index) * 2)
		local Hex2 = buffer.readu16(Lookup, buffer.readu8(Output, Index + 1) * 2)
		local Hex3 = buffer.readu16(Lookup, buffer.readu8(Output, Index + 2) * 2)
		local Hex4 = buffer.readu16(Lookup, buffer.readu8(Output, Index + 3) * 2)
		local Hex5 = buffer.readu16(Lookup, buffer.readu8(Output, Index + 4) * 2)
		local Hex6 = buffer.readu16(Lookup, buffer.readu8(Output, Index + 5) * 2)
		local Hex7 = buffer.readu16(Lookup, buffer.readu8(Output, Index + 6) * 2)
		local Hex8 = buffer.readu16(Lookup, buffer.readu8(Output, Index + 7) * 2)

		buffer.writeu16(Hex, HexCursor, Hex1)
		buffer.writeu16(Hex, HexCursor + 2, Hex2)
		buffer.writeu16(Hex, HexCursor + 4, Hex3)
		buffer.writeu16(Hex, HexCursor + 6, Hex4)
		buffer.writeu16(Hex, HexCursor + 8, Hex5)
		buffer.writeu16(Hex, HexCursor + 10, Hex6)
		buffer.writeu16(Hex, HexCursor + 12, Hex7)
		buffer.writeu16(Hex, HexCursor + 14, Hex8)

		HexCursor += 16
	end

	for Index = OutputBytes - Leftover, OutputBytes - 1 do
		local HexPair = buffer.readu16(Lookup, buffer.readu8(Output, Index) * 2)
		buffer.writeu16(Hex, HexCursor, HexPair)
		HexCursor += 2
	end

	return buffer.tostring(Hex), Output
end

return KMAC


--[=[
	Cryptography library: MD5

	⚠ WARNING: MD5 is cryptographically broken!
	Only use for legacy compatibility, checksums, or non-security purposes.
	For security, use SHA256 or higher. ⚠

	Return type: string
	Usage:
		local Message = buffer.fromstring("Hello World")
		local Result = MD5(Message)
--]=]

--!strict
--!optimize 2
--!native
local OFFSETS = table.create(64) :: {number}
local CONSTANTS = {
	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
}

local SHIFTS = {
	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
}

local function PreProcess(Contents: buffer): (buffer, number)
	local ContentLength = buffer.len(Contents)
	local BitLength = ContentLength * 8

	local Padding = (56 - ((ContentLength + 1) % 64)) % 64

	local NewContentLength = ContentLength + 1 + Padding + 8
	local NewContent = buffer.create(NewContentLength)

	buffer.copy(NewContent, 0, Contents)

	buffer.writeu8(NewContent, ContentLength, 0x80)

	local LengthOffset = ContentLength + 1 + Padding
	for Index = 0, 7 do
		local Byte = BitLength % 256
		buffer.writeu8(NewContent, LengthOffset + Index, Byte)
		BitLength = bit32.rshift(BitLength, 8)
	end

	return NewContent, NewContentLength
end

local function DigestBlocks(Blocks: buffer, Length: number): (number, number, number, number)
	local A, B, C, D = 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476

	local Offsets = OFFSETS
	local Constants = CONSTANTS
	local Shifts = SHIFTS

	for Offset = 0, Length - 1, 64 do
		for WordIndex = 0, 15 do
			local BlockOffset = Offset + WordIndex * 4
			local Word = buffer.readu32(Blocks, BlockOffset)
			Offsets[WordIndex + 1] = Word
		end

		local OldA, OldB, OldC, OldD = A, B, C, D
		local Temp, Func = 0, 0
		for Round = 0, 15 do
			local Chunk = Offsets[Round + 1]
			Func = bit32.bxor(OldD, bit32.band(OldB, bit32.bxor(OldC, OldD)))
			Temp = OldD
			OldD = OldC
			OldC = OldB

			OldB = OldB + bit32.lrotate(OldA + Func + Constants[Round + 1] + Chunk, Shifts[Round + 1])
			OldA = Temp
		end

		for Round = 16, 31 do
			local Chunk = Offsets[(5 * Round + 1) % 16 + 1]
			Func = bit32.bxor(OldC, bit32.band(OldD, bit32.bxor(OldB, OldC)))
			Temp = OldD
			OldD = OldC
			OldC = OldB
			OldB = OldB + bit32.lrotate(OldA + Func + Constants[Round + 1] + Chunk, Shifts[Round + 1])
			OldA = Temp
		end

		for Round = 32, 47 do
			local Chunk = Offsets[(3 * Round + 5) % 16 + 1]
			Func = bit32.bxor(OldB, OldC, OldD)
			Temp = OldD
			OldD = OldC
			OldC = OldB
			OldB = OldB + bit32.lrotate(OldA + Func + Constants[Round + 1] + Chunk, Shifts[Round + 1])
			OldA = Temp
		end

		for Round = 48, 63 do
			local Chunk = Offsets[(7 * Round) % 16 + 1]
			Func = bit32.bxor(OldC, bit32.bor(OldB, bit32.bnot(OldD)))
			Temp = OldD
			OldD = OldC
			OldC = OldB
			OldB = OldB + bit32.lrotate(OldA + Func + Constants[Round + 1] + Chunk, Shifts[Round + 1])
			OldA = Temp
		end

		A = bit32.bor(OldA + A, 0)
		B = bit32.bor(OldB + B, 0)
		C = bit32.bor(OldC + C, 0)
		D = bit32.bor(OldD + D, 0)
	end

	return bit32.byteswap(A), bit32.byteswap(B), bit32.byteswap(C), bit32.byteswap(D)
end

local function MD5(Message: buffer, Salt: buffer?): (string, buffer)
	if Salt and buffer.len(Salt) > 0 then
		local MessageWithSalt = buffer.create(buffer.len(Message) + buffer.len(Salt))
		buffer.copy(MessageWithSalt, 0, Message)
		buffer.copy(MessageWithSalt, buffer.len(Message), Salt)
		Message = MessageWithSalt
	end

	local ProcessedMessage, Length = PreProcess(Message)

	local A, B, C, D = DigestBlocks(ProcessedMessage, Length)
	local Digest = buffer.create(16)

	buffer.writeu32(Digest, 0, A)
	buffer.writeu32(Digest, 4, B)
	buffer.writeu32(Digest, 8, C)
	buffer.writeu32(Digest, 12, D)

	return string.format("%08x%08x%08x%08x", A, B, C, D), Digest
end

return MD5


--[=[
	Cryptography library: MurmurHash3-32

	⚠ WARNING: MurmurHash3 wasn't designed with cryptographic security in mind!
	Only use for non-security purposes like hash tables or hyperloglog. For security, use SHA256 or higher. ⚠

	MurmurHash3 is a fast non-cryptographic hash that is well-distributed.

	Return type: number
	Example usage:
		local Message = buffer.fromstring("Hello World")

		--------Usage Case 1--------
		local Result = MurmurHash3(Message)

		--------Usage Case 2--------
		local Seed = 0xDEADBEEF
		local Result = MurmurHash3(Message, Seed)
--]=]

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

local function Mul32(A: number, B: number): number
	local AHigh = bit32.rshift(A, 16)
	local ALow = bit32.band(A, 0xFFFF)
	local BHigh = bit32.rshift(B, 16)
	local BLow = bit32.band(B, 0xFFFF)

	local LoLo = ALow * BLow
	local HiLo = bit32.lshift(AHigh * BLow, 16)
	local LoHi = bit32.lshift(ALow * BHigh, 16)

	local Result = bit32.bor(LoLo + HiLo, 0)
	return bit32.bor(Result + LoHi, 0)
end

local function FMix32(Hash: number): number
	Hash = Mul32(bit32.bxor(Hash, bit32.rshift(Hash, 16)), 0x85ebca6b)
	Hash = Mul32(bit32.bxor(Hash, bit32.rshift(Hash, 13)), 0xc2b2ae35)
	Hash = bit32.bxor(Hash, bit32.rshift(Hash, 16))
	return Hash
end

local function MurmurHash3(Message: buffer, Seed: number?): number
	local C1 = 0xcc9e2d51
	local C2 = 0x1b873593

	local Hash = bit32.bor(Seed or 0, 0)
	local MessageLength = buffer.len(Message)
	local BlockCount = MessageLength // 4
	local UnrolledBlocks = BlockCount // 4
	local CurrentOffset = 0

	for _ = 1, UnrolledBlocks do
		local K1 = buffer.readu32(Message, CurrentOffset)
		K1 = Mul32(bit32.lrotate(Mul32(K1, 0xcc9e2d51), 15), 0x1b873593)
		Hash = bit32.bor(bit32.lrotate(bit32.bxor(Hash, K1), 13) * 5 + 0xe6546b64, 0)

		local K2 = buffer.readu32(Message, CurrentOffset + 4)
		K2 = Mul32(bit32.lrotate(Mul32(K2, 0xcc9e2d51), 15), 0x1b873593)
		Hash = bit32.bor(bit32.lrotate(bit32.bxor(Hash, K2), 13) * 5 + 0xe6546b64, 0)

		local K3 = buffer.readu32(Message, CurrentOffset + 8)
		K3 = Mul32(bit32.lrotate(Mul32(K3, 0xcc9e2d51), 15), 0x1b873593)
		Hash = bit32.bor(bit32.lrotate(bit32.bxor(Hash, K3), 13) * 5 + 0xe6546b64, 0)

		local K4 = buffer.readu32(Message, CurrentOffset + 12)
		K4 = Mul32(bit32.lrotate(Mul32(K4, 0xcc9e2d51), 15), 0x1b873593)
		Hash = bit32.bor(bit32.lrotate(bit32.bxor(Hash, K4), 13) * 5 + 0xe6546b64, 0)

		CurrentOffset += 16
	end

	local RemainingBlocks = BlockCount % 4
	for _ = 1, RemainingBlocks do
		local K = buffer.readu32(Message, CurrentOffset)
		K = Mul32(K, 0xcc9e2d51)
		K = bit32.lrotate(K, 15)
		K = Mul32(K, 0x1b873593)
		Hash = bit32.bxor(Hash, K)
		Hash = bit32.lrotate(Hash, 13)
		Hash = bit32.bor(Hash * 5 + 0xe6546b64, 0)

		CurrentOffset += 4
	end

	local Remainder = MessageLength % 4
	if Remainder > 0 then
		local K1 = 0

		if Remainder >= 3 then
			K1 = bit32.bxor(K1, bit32.lshift(buffer.readu8(Message, CurrentOffset + 2), 16))
		end

		if Remainder >= 2 then
			K1 = bit32.bxor(K1, bit32.lshift(buffer.readu8(Message, CurrentOffset + 1), 8))
		end

		K1 = bit32.bxor(K1, buffer.readu8(Message, CurrentOffset))

		K1 = Mul32(K1, C1)
		K1 = bit32.lrotate(K1, 15)
		K1 = Mul32(K1, C2)
		Hash = bit32.bxor(Hash, K1)
	end

	Hash = bit32.bxor(Hash, MessageLength)
	Hash = FMix32(Hash)

	return Hash
end

return MurmurHash3


--[=[
	Cryptography library: SHA1

	⚠ WARNING: SHA1 is cryptographically broken!
	Only use for legacy compatibility, checksums, or non-security purposes.
	For security, use SHA256 or higher. ⚠

	Sizes:
		Digest: 20 bytes

	Return type: string
	Example usage:
		local Message = buffer.fromstring("Hello World")

		--------Usage Case 1--------
		local Result = SHA1(Message)

		--------Usage Case 2--------
		local OptionalSalt = buffer.fromstring("Salty")
		local Result = SHA1(Message, OptionalSalt)
--]=]

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

local OFFSETS = buffer.create(320)

local function PreProcess(Contents: buffer): (buffer, number)
	local ContentLength = buffer.len(Contents)
	local Padding = (64 - ((ContentLength + 9) % 64)) % 64

	local NewContentLength = ContentLength + 1 + Padding + 8
	local NewContent = buffer.create(NewContentLength)
	buffer.copy(NewContent, 0, Contents)
	buffer.writeu8(NewContent, ContentLength, 128)

	local Length8 = ContentLength * 8
	for Index = 7, 0, -1 do
		local Remainder = Length8 % 256
		buffer.writeu8(NewContent, Index + ContentLength + 1 + Padding, Remainder)
		Length8 = (Length8 - Remainder) / 256
	end

	return NewContent, NewContentLength
end

local function DigestBlocks(Blocks: buffer, Length: number): (number, number, number, number, number)
	local A, B, C, D, E = 0x67452301, 0xefcdaB89, 0x98badcfe, 0x10325476, 0xc3d2e1f0
	local Offsets = OFFSETS

	for Offset = 0, Length - 1, 64 do
		for BlockIndex = 0, 60, 4 do
			buffer.writeu32(Offsets, BlockIndex, bit32.byteswap(buffer.readu32(Blocks, Offset + BlockIndex)))
		end

		for Index = 64, 316, 4 do
			buffer.writeu32(Offsets, Index, bit32.lrotate(bit32.bxor(
				buffer.readu32(Offsets, Index - 12),
				buffer.readu32(Offsets, Index - 32),
				buffer.readu32(Offsets, Index - 56),
				buffer.readu32(Offsets, Index - 64)
			), 1))
		end

		local H1, H2, H3, H4, H5 = A, B, C, D, E

		local Temp
		for Round = 0, 19 do
			Temp = bit32.lrotate(H1, 5) +
				bit32.band(H2, H3) + bit32.band(-1 - H2, H4) +
				H5 + 0x5a827999 +
				buffer.readu32(Offsets, Round * 4)

			H5, H4, H3, H2, H1 = H4, H3, bit32.lrotate(H2, 30), H1, Temp
		end

		for Round = 20, 39 do
			Temp = bit32.lrotate(H1, 5) +
				bit32.bxor(H2, H3, H4) +
				H5 + 0x6ed9eba1 +
				buffer.readu32(Offsets, Round * 4)

			H5, H4, H3, H2, H1 = H4, H3, bit32.lrotate(H2, 30), H1, Temp
		end

		for Round = 40, 59 do
			Temp = bit32.lrotate(H1, 5) +
				bit32.band(H4, H3) + bit32.band(H2, bit32.bxor(H4, H3)) +
				H5 + 0x8f1bbcdc +
				buffer.readu32(Offsets, Round * 4)

			H5, H4, H3, H2, H1 = H4, H3, bit32.lrotate(H2, 30), H1, Temp
		end

		for Round = 60, 79 do
			Temp = bit32.lrotate(H1, 5) +
				bit32.bxor(H2, H3, H4) +
				H5 + 0xca62c1d6 +
				buffer.readu32(Offsets, Round * 4)

			H5, H4, H3, H2, H1 = H4, H3, bit32.lrotate(H2, 30), H1, Temp
		end

		A = bit32.bor(A + H1, 0)
		B = bit32.bor(B + H2, 0)
		C = bit32.bor(C + H3, 0)
		D = bit32.bor(D + H4, 0)
		E = bit32.bor(E + H5, 0)
	end

	return A, B, C, D, E
end

local function SHA1(Message: buffer, Salt: buffer?): string
	if Salt and buffer.len(Salt) > 0 then
		local MessageWithSalt = buffer.create(buffer.len(Message) + buffer.len(Salt))

		buffer.copy(MessageWithSalt, 0, Message)
		buffer.copy(MessageWithSalt, buffer.len(Message), Salt)

		Message = MessageWithSalt
	end

	local ProcessedMessage, Length = PreProcess(Message)
	return string.format("%08x%08x%08x%08x%08x", DigestBlocks(ProcessedMessage, Length))
end

return SHA1


--[=[
	Cryptography library: SHA224

	Sizes:
		Digest: 28 bytes

	Return type: string
	Example usage:
		local Message = buffer.fromstring("Hello World")
		local Result = SHA224(Message)
--]=]

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

local CONSTANTS = buffer.create(256) do -- CONSTANTS = k
	local RoundConstants = {
		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
	}

	for Index, Constant in ipairs(RoundConstants) do
		local BufferOffset = (Index - 1) * 4
		buffer.writeu32(CONSTANTS, BufferOffset, Constant)
	end
end

local function PreProcess(Contents: buffer): (buffer, number)
	local ContentLength = buffer.len(Contents)
	local Padding = -(ContentLength + 9) % 64

	local NewContentLength = ContentLength + 1 + Padding + 8
	local NewContent = buffer.create(NewContentLength)
	buffer.copy(NewContent, 0, Contents)
	buffer.writeu8(NewContent, ContentLength, 128)
	local Length8 = ContentLength * 8
	for Index = 7, 0, -1 do
		local Remainder = Length8 % 256
		buffer.writeu8(NewContent, Index + ContentLength + 1 + Padding, Remainder)
		Length8 = (Length8 - Remainder) / 256
	end

	return NewContent, NewContentLength
end

local OFFSETS = buffer.create(256)
local function DigestBlocks(Blocks: buffer, Length: number): (number, number, number, number, number, number, number)
	local A, B, C, D = 0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939
	local E, F, G, H = 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4

	local Offsets = OFFSETS
	local Constants = CONSTANTS

	for Offset = 0, Length - 1, 64 do
		for BlockIndex = 0, 60, 4 do
			buffer.writeu32(Offsets, BlockIndex, bit32.byteswap(buffer.readu32(Blocks, Offset + BlockIndex)))
		end

		for Index = 64, 252, 4 do
			local Sub15 = buffer.readu32(Offsets, Index - 60)
			local S0 = bit32.bxor(bit32.rrotate(Sub15, 7), bit32.rrotate(Sub15, 18), bit32.rshift(Sub15, 3))

			local Sub2 = buffer.readu32(Offsets, Index - 8)
			local S1 = bit32.bxor(bit32.rrotate(Sub2, 17), bit32.rrotate(Sub2, 19), bit32.rshift(Sub2, 10))

			local Sub7, Sub16 = buffer.readu32(Offsets, Index - 28), buffer.readu32(Offsets, Index - 64)
			buffer.writeu32(Offsets, Index, (Sub16 + S0 + Sub7 + S1))
		end

		local OldA, OldB, OldC, OldD, OldE, OldF, OldG, OldH = A, B, C, D, E, F, G, H

		for BufferIndex = 0, 252, 4 do
			local S1 = bit32.bxor(bit32.rrotate(E, 6), bit32.rrotate(E, 11), bit32.rrotate(E, 25))
			local Ch = bit32.bxor(bit32.band(E, F), bit32.band(bit32.bnot(E), G))
			local Temp1 = H + S1 + Ch + buffer.readu32(Constants, BufferIndex) + buffer.readu32(Offsets, BufferIndex)
			H, G, F, E, D = G, F, E, D + Temp1, C

			local S0 = bit32.bxor(bit32.rrotate(A, 2), bit32.rrotate(A, 13), bit32.rrotate(A, 22))
			local Maj = bit32.bxor(bit32.band(A, B), bit32.band(A, C), bit32.band(B, C))
			C, B, A = B, A, Temp1 + S0 + Maj
		end

		A, B, C, D, E, F, G, H =
			bit32.bor(A + OldA, 0),
			bit32.bor(B + OldB, 0),
			bit32.bor(C + OldC, 0),
			bit32.bor(D + OldD, 0),
			bit32.bor(E + OldE, 0),
			bit32.bor(F + OldF, 0),
			bit32.bor(G + OldG, 0),
			bit32.bor(H + OldH, 0)
	end

	return A, B, C, D, E, F, G
end

local function SHA224(Message: buffer): (string, buffer)
	local ProcessedMessage, Length = PreProcess(Message)
	local A, B, C, D, E, F, G = DigestBlocks(ProcessedMessage, Length)

	local Digest = buffer.create(28)

	buffer.writeu32(Digest, 0, A)
	buffer.writeu32(Digest, 4, B)
	buffer.writeu32(Digest, 8, C)
	buffer.writeu32(Digest, 12, D)
	buffer.writeu32(Digest, 16, E)
	buffer.writeu32(Digest, 20, F)
	buffer.writeu32(Digest, 24, G)

	return string.format("%08x%08x%08x%08x%08x%08x%08x", A, B, C, D, E, F, G), Digest
end

return SHA224


--[=[
	Cryptography library: SHA256

	Sizes:
		Digest: 32 bytes

	Return type: string
	Example usage:
		local Message = buffer.fromstring("Hello World")
		local Result = SHA256(Message)
--]=]

--!strict
--!optimize 2
--!native
local CONSTANTS = buffer.create(256) do -- CONSTANTS = k
	local RoundConstants = {
		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
	}

	for Index, Constant in ipairs(RoundConstants) do
		local BufferOffset = (Index - 1) * 4
		buffer.writeu32(CONSTANTS, BufferOffset, Constant)
	end
end

local function PreProcess(Contents: buffer): (buffer, number)
	local ContentLength = buffer.len(Contents)
	local Padding = -(ContentLength + 9) % 64

	local NewContentLength = ContentLength + 1 + Padding + 8
	local NewContent = buffer.create(NewContentLength)
	buffer.copy(NewContent, 0, Contents)
	buffer.writeu8(NewContent, ContentLength, 128)
	local Length8 = ContentLength * 8
	for Index = 7, 0, -1 do
		local Remainder = Length8 % 256
		buffer.writeu8(NewContent, Index + ContentLength + 1 + Padding, Remainder)
		Length8 = (Length8 - Remainder) / 256
	end

	return NewContent, NewContentLength
end

local OFFSETS = buffer.create(256)
local function DigestBlocks(Blocks: buffer, Length: number): (number, number, number, number, number, number, number, number)
	local A, B, C, D = 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a
	local E, F, G, H = 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19

	local Offsets = OFFSETS
	local Constants = CONSTANTS

	for Offset = 0, Length - 1, 64 do
		for BlockIndex = 0, 60, 4 do
			buffer.writeu32(Offsets, BlockIndex, bit32.byteswap(buffer.readu32(Blocks, Offset + BlockIndex)))
		end

		for Index = 64, 252, 4 do
			local Sub15 = buffer.readu32(Offsets, Index - 60)
			local S0 = bit32.bxor(bit32.rrotate(Sub15, 7), bit32.rrotate(Sub15, 18), bit32.rshift(Sub15, 3))

			local Sub2 = buffer.readu32(Offsets, Index - 8)
			local S1 = bit32.bxor(bit32.rrotate(Sub2, 17), bit32.rrotate(Sub2, 19), bit32.rshift(Sub2, 10))

			local Sub7, Sub16 = buffer.readu32(Offsets, Index - 28), buffer.readu32(Offsets, Index - 64)
			buffer.writeu32(Offsets, Index, (Sub16 + S0 + Sub7 + S1))
		end

		local OldA, OldB, OldC, OldD, OldE, OldF, OldG, OldH = A, B, C, D, E, F, G, H

		for BufferIndex = 0, 252, 4 do
			local S1 = bit32.bxor(bit32.rrotate(E, 6), bit32.rrotate(E, 11), bit32.rrotate(E, 25))
			local Ch = bit32.bxor(bit32.band(E, F), bit32.band(bit32.bnot(E), G))
			local Temp1 = H + S1 + Ch + buffer.readu32(Constants, BufferIndex) + buffer.readu32(Offsets, BufferIndex)
			H, G, F, E, D = G, F, E, D + Temp1, C

			local S0 = bit32.bxor(bit32.rrotate(A, 2), bit32.rrotate(A, 13), bit32.rrotate(A, 22))
			local Maj = bit32.bxor(bit32.band(A, B), bit32.band(A, C), bit32.band(B, C))
			C, B, A = B, A, Temp1 + S0 + Maj
		end

		A, B, C, D, E, F, G, H =
			bit32.bor(A + OldA, 0),
			bit32.bor(B + OldB, 0),
			bit32.bor(C + OldC, 0),
			bit32.bor(D + OldD, 0),
			bit32.bor(E + OldE, 0),
			bit32.bor(F + OldF, 0),
			bit32.bor(G + OldG, 0),
			bit32.bor(H + OldH, 0) :: number
	end

	return A, B, C, D, E, F, G, H
end

local function SHA256(Message: buffer): (string, buffer)
	local ProcessedMessage, Length = PreProcess(Message)
	local A, B, C, D, E, F, G, H = DigestBlocks(ProcessedMessage, Length)

	local Digest = buffer.create(32)

	buffer.writeu32(Digest, 0, A)
	buffer.writeu32(Digest, 4, B)
	buffer.writeu32(Digest, 8, C)
	buffer.writeu32(Digest, 12, D)
	buffer.writeu32(Digest, 16, E)
	buffer.writeu32(Digest, 20, F)
	buffer.writeu32(Digest, 24, G)
	buffer.writeu32(Digest, 28, H)

	return string.format("%08x%08x%08x%08x%08x%08x%08x%08x", A, B, C, D, E, F, G, H), Digest
end

return SHA256


--[[
	Cryptography library: SHA2 Family
--]]

return {
	SHA224 = require("@self/SHA224"),
	SHA256 = require("@self/SHA256"),
	SHA384 = require("@self/SHA384"),
	SHA512 = require("@self/SHA512"),
}


--[=[
	Cryptography library: Keccak/SHA-3

	Sizes:
		SHA3-224 Digest: 28 bytes
		SHA3-256 Digest: 32 bytes
		SHA3-384 Digest: 48 bytes
		SHA3-512 Digest: 64 bytes
		SHAKE128 Digest: variable
		SHAKE256 Digest: variable

	Return type: string
	Example usage:
		local Message = buffer.fromstring("Hello World")

		local Result1 = SHA3_256(Message)
		local Result2 = SHAKE128(Message, 32)
--]=]

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

local SHA3 = {}

local ENCODE_LOOKUP = buffer.create(256 * 2) do
	local HexChars = "0123456789abcdef"
	for Byte = 0, 255 do
		local HighNibble = bit32.rshift(Byte, 4)
		local LowNibble = Byte % 16

		local HighChar = string.byte(HexChars, HighNibble + 1)
		local LowChar = string.byte(HexChars, LowNibble + 1)

		local Combined = HighChar + bit32.lshift(LowChar, 8)
		buffer.writeu16(ENCODE_LOOKUP, Byte * 2, Combined)
	end
end

local LOW_ROUND, HIGH_ROUND = buffer.create(96), buffer.create(96) do
	local HighFactorKeccak = 0
	local ShiftRegister = 29
	local function GetNextBit(): number
		local Result = ShiftRegister % 2
		ShiftRegister = bit32.bxor((ShiftRegister - Result) // 2, 142 * Result)

		return Result
	end

	for Index = 0, 23 do
		local LowValue = 0
		local Multiplier: number

		for _ = 1, 6 do
			Multiplier = if Multiplier then Multiplier * Multiplier * 2 else 1
			LowValue += GetNextBit() * Multiplier
		end

		local HighValue = GetNextBit() * Multiplier
		buffer.writeu32(HIGH_ROUND, Index * 4, HighValue)
		buffer.writeu32(LOW_ROUND, Index * 4, LowValue + HighValue * HighFactorKeccak)
	end
end

local LANES_LOW = buffer.create(100)
local LANES_HIGH = buffer.create(100)

local function Keccak(LanesLow: buffer, LanesHigh: buffer, InputBuffer: buffer, Offset: number, Size: number, BlockSizeInBytes: number): ()
	local QuadWordsQuantity = BlockSizeInBytes // 8
	local RCHigh, RCLow = HIGH_ROUND, LOW_ROUND

	for Position = Offset, Offset + Size - 1, BlockSizeInBytes do
		for Index = 0, (QuadWordsQuantity - 1) * 4, 4 do
			local BufferPos = Position + Index * 2

			buffer.writeu32(LanesLow, Index, bit32.bxor(
				buffer.readu32(LanesLow, Index),
				buffer.readu32(InputBuffer, BufferPos)
				))

			buffer.writeu32(LanesHigh, Index, bit32.bxor(
				buffer.readu32(LanesHigh, Index),
				buffer.readu32(InputBuffer, BufferPos + 4)
				))
		end

		local Lane01Low, Lane01High = buffer.readu32(LanesLow, 0), buffer.readu32(LanesHigh, 0)
		local Lane02Low, Lane02High = buffer.readu32(LanesLow, 4), buffer.readu32(LanesHigh, 4)
		local Lane03Low, Lane03High = buffer.readu32(LanesLow, 8), buffer.readu32(LanesHigh, 8)

		local Lane04Low, Lane04High = buffer.readu32(LanesLow, 12), buffer.readu32(LanesHigh, 12)
		local Lane05Low, Lane05High = buffer.readu32(LanesLow, 16), buffer.readu32(LanesHigh, 16)
		local Lane06Low, Lane06High = buffer.readu32(LanesLow, 20), buffer.readu32(LanesHigh, 20)

		local Lane07Low, Lane07High = buffer.readu32(LanesLow, 24), buffer.readu32(LanesHigh, 24)
		local Lane08Low, Lane08High = buffer.readu32(LanesLow, 28), buffer.readu32(LanesHigh, 28)
		local Lane09Low, Lane09High = buffer.readu32(LanesLow, 32), buffer.readu32(LanesHigh, 32)

		local Lane10Low, Lane10High = buffer.readu32(LanesLow, 36), buffer.readu32(LanesHigh, 36)
		local Lane11Low, Lane11High = buffer.readu32(LanesLow, 40), buffer.readu32(LanesHigh, 40)
		local Lane12Low, Lane12High = buffer.readu32(LanesLow, 44), buffer.readu32(LanesHigh, 44)

		local Lane13Low, Lane13High = buffer.readu32(LanesLow, 48), buffer.readu32(LanesHigh, 48)
		local Lane14Low, Lane14High = buffer.readu32(LanesLow, 52), buffer.readu32(LanesHigh, 52)
		local Lane15Low, Lane15High = buffer.readu32(LanesLow, 56), buffer.readu32(LanesHigh, 56)

		local Lane16Low, Lane16High = buffer.readu32(LanesLow, 60), buffer.readu32(LanesHigh, 60)
		local Lane17Low, Lane17High = buffer.readu32(LanesLow, 64), buffer.readu32(LanesHigh, 64)
		local Lane18Low, Lane18High = buffer.readu32(LanesLow, 68), buffer.readu32(LanesHigh, 68)

		local Lane19Low, Lane19High = buffer.readu32(LanesLow, 72), buffer.readu32(LanesHigh, 72)
		local Lane20Low, Lane20High = buffer.readu32(LanesLow, 76), buffer.readu32(LanesHigh, 76)
		local Lane21Low, Lane21High = buffer.readu32(LanesLow, 80), buffer.readu32(LanesHigh, 80)

		local Lane22Low, Lane22High = buffer.readu32(LanesLow, 84), buffer.readu32(LanesHigh, 84)
		local Lane23Low, Lane23High = buffer.readu32(LanesLow, 88), buffer.readu32(LanesHigh, 88)
		local Lane24Low, Lane24High = buffer.readu32(LanesLow, 92), buffer.readu32(LanesHigh, 92)

		local Lane25Low, Lane25High = buffer.readu32(LanesLow, 96), buffer.readu32(LanesHigh, 96)

		for RoundIndex = 0, 92, 4 do
			local Column1Low, Column1High = bit32.bxor(Lane01Low, Lane06Low, Lane11Low, Lane16Low, Lane21Low), bit32.bxor(Lane01High, Lane06High, Lane11High, Lane16High, Lane21High)
			local Column2Low, Column2High = bit32.bxor(Lane02Low, Lane07Low, Lane12Low, Lane17Low, Lane22Low), bit32.bxor(Lane02High, Lane07High, Lane12High, Lane17High, Lane22High)
			local Column3Low, Column3High = bit32.bxor(Lane03Low, Lane08Low, Lane13Low, Lane18Low, Lane23Low), bit32.bxor(Lane03High, Lane08High, Lane13High, Lane18High, Lane23High)
			local Column4Low, Column4High = bit32.bxor(Lane04Low, Lane09Low, Lane14Low, Lane19Low, Lane24Low), bit32.bxor(Lane04High, Lane09High, Lane14High, Lane19High, Lane24High)
			local Column5Low, Column5High = bit32.bxor(Lane05Low, Lane10Low, Lane15Low, Lane20Low, Lane25Low), bit32.bxor(Lane05High, Lane10High, Lane15High, Lane20High, Lane25High)

			local DeltaLow, DeltaHigh = bit32.bxor(Column1Low, Column3Low * 2 + Column3High // 2147483648), bit32.bxor(Column1High, Column3High * 2 + Column3Low // 2147483648)
			local Temp0Low, Temp0High = bit32.bxor(DeltaLow, Lane02Low), bit32.bxor(DeltaHigh, Lane02High)
			local Temp1Low, Temp1High = bit32.bxor(DeltaLow, Lane07Low), bit32.bxor(DeltaHigh, Lane07High)
			local Temp2Low, Temp2High = bit32.bxor(DeltaLow, Lane12Low), bit32.bxor(DeltaHigh, Lane12High)
			local Temp3Low, Temp3High = bit32.bxor(DeltaLow, Lane17Low), bit32.bxor(DeltaHigh, Lane17High)
			local Temp4Low, Temp4High = bit32.bxor(DeltaLow, Lane22Low), bit32.bxor(DeltaHigh, Lane22High)

			Lane02Low = Temp1Low // 1048576 + (Temp1High * 4096); Lane02High = Temp1High // 1048576 + (Temp1Low * 4096)
			Lane07Low = Temp3Low // 524288 + (Temp3High * 8192); Lane07High = Temp3High // 524288 + (Temp3Low * 8192)
			Lane12Low = Temp0Low * 2 + Temp0High // 2147483648; Lane12High = Temp0High * 2 + Temp0Low // 2147483648
			Lane17Low = Temp2Low * 1024 + Temp2High // 4194304; Lane17High = Temp2High * 1024 + Temp2Low // 4194304
			Lane22Low = Temp4Low * 4 + Temp4High // 1073741824; Lane22High = Temp4High * 4 + Temp4Low // 1073741824

			DeltaLow = bit32.bxor(Column2Low, Column4Low * 2 + Column4High // 2147483648); DeltaHigh = bit32.bxor(Column2High, Column4High * 2 + Column4Low // 2147483648)
			Temp0Low = bit32.bxor(DeltaLow, Lane03Low); Temp0High = bit32.bxor(DeltaHigh, Lane03High)
			Temp1Low = bit32.bxor(DeltaLow, Lane08Low); Temp1High = bit32.bxor(DeltaHigh, Lane08High)
			Temp2Low = bit32.bxor(DeltaLow, Lane13Low); Temp2High = bit32.bxor(DeltaHigh, Lane13High)
			Temp3Low = bit32.bxor(DeltaLow, Lane18Low); Temp3High = bit32.bxor(DeltaHigh, Lane18High)
			Temp4Low = bit32.bxor(DeltaLow, Lane23Low); Temp4High = bit32.bxor(DeltaHigh, Lane23High)

			Lane03Low = Temp2Low // 2097152 + (Temp2High * 2048); Lane03High = Temp2High // 2097152 + (Temp2Low * 2048)
			Lane08Low = Temp4Low // 8 + bit32.bor(Temp4High * 536870912, 0); Lane08High = Temp4High // 8 + bit32.bor(Temp4Low * 536870912, 0)
			Lane13Low = Temp1Low * 64 + Temp1High // 67108864; Lane13High = Temp1High * 64 + Temp1Low // 67108864
			Lane18Low = (Temp3Low * 32768) + Temp3High // 131072; Lane18High = (Temp3High * 32768) + Temp3Low // 131072
			Lane23Low = Temp0Low // 4 + bit32.bor(Temp0High * 1073741824, 0); Lane23High = Temp0High // 4 + bit32.bor(Temp0Low * 1073741824, 0)

			DeltaLow = bit32.bxor(Column3Low, Column5Low * 2 + Column5High // 2147483648); DeltaHigh = bit32.bxor(Column3High, Column5High * 2 + Column5Low // 2147483648)
			Temp0Low = bit32.bxor(DeltaLow, Lane04Low); Temp0High = bit32.bxor(DeltaHigh, Lane04High)
			Temp1Low = bit32.bxor(DeltaLow, Lane09Low); Temp1High = bit32.bxor(DeltaHigh, Lane09High)
			Temp2Low = bit32.bxor(DeltaLow, Lane14Low); Temp2High = bit32.bxor(DeltaHigh, Lane14High)
			Temp3Low = bit32.bxor(DeltaLow, Lane19Low); Temp3High = bit32.bxor(DeltaHigh, Lane19High)
			Temp4Low = bit32.bxor(DeltaLow, Lane24Low); Temp4High = bit32.bxor(DeltaHigh, Lane24High)

			Lane04Low = bit32.bor(Temp3Low * 2097152, 0) + Temp3High // 2048; Lane04High = bit32.bor(Temp3High * 2097152, 0) + Temp3Low // 2048
			Lane09Low = bit32.bor(Temp0Low * 268435456, 0) + Temp0High // 16; Lane09High = bit32.bor(Temp0High * 268435456, 0) + Temp0Low // 16
			Lane14Low = bit32.bor(Temp2Low * 33554432, 0) + Temp2High // 128; Lane14High = bit32.bor(Temp2High * 33554432, 0) + Temp2Low // 128
			Lane19Low = Temp4Low // 256 + bit32.bor(Temp4High * 16777216, 0); Lane19High = Temp4High // 256 + bit32.bor(Temp4Low * 16777216, 0)
			Lane24Low = Temp1Low // 512 + bit32.bor(Temp1High * 8388608, 0); Lane24High = Temp1High // 512 + bit32.bor(Temp1Low * 8388608, 0)
			DeltaLow = bit32.bxor(Column4Low, Column1Low * 2 + Column1High // 2147483648); DeltaHigh = bit32.bxor(Column4High, Column1High * 2 + Column1Low // 2147483648)

			Temp0Low = bit32.bxor(DeltaLow, Lane05Low); Temp0High = bit32.bxor(DeltaHigh, Lane05High)
			Temp1Low = bit32.bxor(DeltaLow, Lane10Low); Temp1High = bit32.bxor(DeltaHigh, Lane10High)
			Temp2Low = bit32.bxor(DeltaLow, Lane15Low); Temp2High = bit32.bxor(DeltaHigh, Lane15High)
			Temp3Low = bit32.bxor(DeltaLow, Lane20Low); Temp3High = bit32.bxor(DeltaHigh, Lane20High)
			Temp4Low = bit32.bxor(DeltaLow, Lane25Low); Temp4High = bit32.bxor(DeltaHigh, Lane25High)

			Lane05Low = (Temp4Low * 16384) + Temp4High // 262144; Lane05High = (Temp4High * 16384) + Temp4Low // 262144
			Lane10Low = bit32.bor(Temp1Low * 1048576, 0) + Temp1High // 4096; Lane10High = bit32.bor(Temp1High * 1048576, 0) + Temp1Low // 4096
			Lane15Low = Temp3Low * 256 + Temp3High // 16777216; Lane15High = Temp3High * 256 + Temp3Low // 16777216
			Lane20Low = bit32.bor(Temp0Low * 134217728, 0) + Temp0High // 32; Lane20High = bit32.bor(Temp0High * 134217728, 0) + Temp0Low // 32
			Lane25Low = Temp2Low // 33554432 + Temp2High * 128; Lane25High = Temp2High // 33554432 + Temp2Low * 128

			DeltaLow = bit32.bxor(Column5Low, Column2Low * 2 + Column2High // 2147483648); DeltaHigh = bit32.bxor(Column5High, Column2High * 2 + Column2Low // 2147483648)
			Temp1Low = bit32.bxor(DeltaLow, Lane06Low); Temp1High = bit32.bxor(DeltaHigh, Lane06High)
			Temp2Low = bit32.bxor(DeltaLow, Lane11Low); Temp2High = bit32.bxor(DeltaHigh, Lane11High)
			Temp3Low = bit32.bxor(DeltaLow, Lane16Low); Temp3High = bit32.bxor(DeltaHigh, Lane16High)
			Temp4Low = bit32.bxor(DeltaLow, Lane21Low); Temp4High = bit32.bxor(DeltaHigh, Lane21High)
			Lane06Low = Temp2Low * 8 + Temp2High // 536870912; Lane06High = Temp2High * 8 + Temp2Low // 536870912
			Lane11Low = (Temp4Low * 262144) + Temp4High // 16384; Lane11High = (Temp4High * 262144) + Temp4Low // 16384
			Lane16Low = Temp1Low // 268435456 + Temp1High * 16; Lane16High = Temp1High // 268435456 + Temp1Low * 16
			Lane21Low = Temp3Low // 8388608 + Temp3High * 512; Lane21High = Temp3High // 8388608 + Temp3Low * 512
			Lane01Low = bit32.bxor(DeltaLow, Lane01Low); Lane01High = bit32.bxor(DeltaHigh, Lane01High)

			Lane01Low, Lane02Low, Lane03Low, Lane04Low, Lane05Low = bit32.bxor(Lane01Low, bit32.band(-1 - Lane02Low, Lane03Low)), bit32.bxor(Lane02Low, bit32.band(-1 - Lane03Low, Lane04Low)), bit32.bxor(Lane03Low, bit32.band(-1 - Lane04Low, Lane05Low)), bit32.bxor(Lane04Low, bit32.band(-1 - Lane05Low, Lane01Low)), bit32.bxor(Lane05Low, bit32.band(-1 - Lane01Low, Lane02Low)) :: number
			Lane01High, Lane02High, Lane03High, Lane04High, Lane05High = bit32.bxor(Lane01High, bit32.band(-1 - Lane02High, Lane03High)), bit32.bxor(Lane02High, bit32.band(-1 - Lane03High, Lane04High)), bit32.bxor(Lane03High, bit32.band(-1 - Lane04High, Lane05High)), bit32.bxor(Lane04High, bit32.band(-1 - Lane05High, Lane01High)), bit32.bxor(Lane05High, bit32.band(-1 - Lane01High, Lane02High)) :: number
			Lane06Low, Lane07Low, Lane08Low, Lane09Low, Lane10Low = bit32.bxor(Lane09Low, bit32.band(-1 - Lane10Low, Lane06Low)), bit32.bxor(Lane10Low, bit32.band(-1 - Lane06Low, Lane07Low)), bit32.bxor(Lane06Low, bit32.band(-1 - Lane07Low, Lane08Low)), bit32.bxor(Lane07Low, bit32.band(-1 - Lane08Low, Lane09Low)), bit32.bxor(Lane08Low, bit32.band(-1 - Lane09Low, Lane10Low)) :: number
			Lane06High, Lane07High, Lane08High, Lane09High, Lane10High = bit32.bxor(Lane09High, bit32.band(-1 - Lane10High, Lane06High)), bit32.bxor(Lane10High, bit32.band(-1 - Lane06High, Lane07High)), bit32.bxor(Lane06High, bit32.band(-1 - Lane07High, Lane08High)), bit32.bxor(Lane07High, bit32.band(-1 - Lane08High, Lane09High)), bit32.bxor(Lane08High, bit32.band(-1 - Lane09High, Lane10High)) :: number
			Lane11Low, Lane12Low, Lane13Low, Lane14Low, Lane15Low = bit32.bxor(Lane12Low, bit32.band(-1 - Lane13Low, Lane14Low)), bit32.bxor(Lane13Low, bit32.band(-1 - Lane14Low, Lane15Low)), bit32.bxor(Lane14Low, bit32.band(-1 - Lane15Low, Lane11Low)), bit32.bxor(Lane15Low, bit32.band(-1 - Lane11Low, Lane12Low)), bit32.bxor(Lane11Low, bit32.band(-1 - Lane12Low, Lane13Low)) :: number
			Lane11High, Lane12High, Lane13High, Lane14High, Lane15High = bit32.bxor(Lane12High, bit32.band(-1 - Lane13High, Lane14High)), bit32.bxor(Lane13High, bit32.band(-1 - Lane14High, Lane15High)), bit32.bxor(Lane14High, bit32.band(-1 - Lane15High, Lane11High)), bit32.bxor(Lane15High, bit32.band(-1 - Lane11High, Lane12High)), bit32.bxor(Lane11High, bit32.band(-1 - Lane12High, Lane13High)) :: number
			Lane16Low, Lane17Low, Lane18Low, Lane19Low, Lane20Low = bit32.bxor(Lane20Low, bit32.band(-1 - Lane16Low, Lane17Low)), bit32.bxor(Lane16Low, bit32.band(-1 - Lane17Low, Lane18Low)), bit32.bxor(Lane17Low, bit32.band(-1 - Lane18Low, Lane19Low)), bit32.bxor(Lane18Low, bit32.band(-1 - Lane19Low, Lane20Low)), bit32.bxor(Lane19Low, bit32.band(-1 - Lane20Low, Lane16Low)) :: number
			Lane16High, Lane17High, Lane18High, Lane19High, Lane20High = bit32.bxor(Lane20High, bit32.band(-1 - Lane16High, Lane17High)), bit32.bxor(Lane16High, bit32.band(-1 - Lane17High, Lane18High)), bit32.bxor(Lane17High, bit32.band(-1 - Lane18High, Lane19High)), bit32.bxor(Lane18High, bit32.band(-1 - Lane19High, Lane20High)), bit32.bxor(Lane19High, bit32.band(-1 - Lane20High, Lane16High)) :: number
			Lane21Low, Lane22Low, Lane23Low, Lane24Low, Lane25Low = bit32.bxor(Lane23Low, bit32.band(-1 - Lane24Low, Lane25Low)), bit32.bxor(Lane24Low, bit32.band(-1 - Lane25Low, Lane21Low)), bit32.bxor(Lane25Low, bit32.band(-1 - Lane21Low, Lane22Low)), bit32.bxor(Lane21Low, bit32.band(-1 - Lane22Low, Lane23Low)), bit32.bxor(Lane22Low, bit32.band(-1 - Lane23Low, Lane24Low)) :: number
			Lane21High, Lane22High, Lane23High, Lane24High, Lane25High = bit32.bxor(Lane23High, bit32.band(-1 - Lane24High, Lane25High)), bit32.bxor(Lane24High, bit32.band(-1 - Lane25High, Lane21High)), bit32.bxor(Lane25High, bit32.band(-1 - Lane21High, Lane22High)), bit32.bxor(Lane21High, bit32.band(-1 - Lane22High, Lane23High)), bit32.bxor(Lane22High, bit32.band(-1 - Lane23High, Lane24High)) :: number

			Lane01Low = bit32.bxor(Lane01Low, buffer.readu32(RCLow, RoundIndex))
			Lane01High = bit32.bxor(Lane01High, buffer.readu32(RCHigh, RoundIndex))
		end

		buffer.writeu32(LanesLow, 0, Lane01Low); buffer.writeu32(LanesHigh, 0, Lane01High)
		buffer.writeu32(LanesLow, 4, Lane02Low); buffer.writeu32(LanesHigh, 4, Lane02High)
		buffer.writeu32(LanesLow, 8, Lane03Low); buffer.writeu32(LanesHigh, 8, Lane03High)
		buffer.writeu32(LanesLow, 12, Lane04Low); buffer.writeu32(LanesHigh, 12, Lane04High)
		buffer.writeu32(LanesLow, 16, Lane05Low); buffer.writeu32(LanesHigh, 16, Lane05High)
		buffer.writeu32(LanesLow, 20, Lane06Low); buffer.writeu32(LanesHigh, 20, Lane06High)
		buffer.writeu32(LanesLow, 24, Lane07Low); buffer.writeu32(LanesHigh, 24, Lane07High)
		buffer.writeu32(LanesLow, 28, Lane08Low); buffer.writeu32(LanesHigh, 28, Lane08High)
		buffer.writeu32(LanesLow, 32, Lane09Low); buffer.writeu32(LanesHigh, 32, Lane09High)
		buffer.writeu32(LanesLow, 36, Lane10Low); buffer.writeu32(LanesHigh, 36, Lane10High)
		buffer.writeu32(LanesLow, 40, Lane11Low); buffer.writeu32(LanesHigh, 40, Lane11High)
		buffer.writeu32(LanesLow, 44, Lane12Low); buffer.writeu32(LanesHigh, 44, Lane12High)
		buffer.writeu32(LanesLow, 48, Lane13Low); buffer.writeu32(LanesHigh, 48, Lane13High)
		buffer.writeu32(LanesLow, 52, Lane14Low); buffer.writeu32(LanesHigh, 52, Lane14High)
		buffer.writeu32(LanesLow, 56, Lane15Low); buffer.writeu32(LanesHigh, 56, Lane15High)
		buffer.writeu32(LanesLow, 60, Lane16Low); buffer.writeu32(LanesHigh, 60, Lane16High)
		buffer.writeu32(LanesLow, 64, Lane17Low); buffer.writeu32(LanesHigh, 64, Lane17High)
		buffer.writeu32(LanesLow, 68, Lane18Low); buffer.writeu32(LanesHigh, 68, Lane18High)
		buffer.writeu32(LanesLow, 72, Lane19Low); buffer.writeu32(LanesHigh, 72, Lane19High)
		buffer.writeu32(LanesLow, 76, Lane20Low); buffer.writeu32(LanesHigh, 76, Lane20High)
		buffer.writeu32(LanesLow, 80, Lane21Low); buffer.writeu32(LanesHigh, 80, Lane21High)
		buffer.writeu32(LanesLow, 84, Lane22Low); buffer.writeu32(LanesHigh, 84, Lane22High)
		buffer.writeu32(LanesLow, 88, Lane23Low); buffer.writeu32(LanesHigh, 88, Lane23High)
		buffer.writeu32(LanesLow, 92, Lane24Low); buffer.writeu32(LanesHigh, 92, Lane24High)
		buffer.writeu32(LanesLow, 96, Lane25Low); buffer.writeu32(LanesHigh, 96, Lane25High)
	end
end

local function ProcessSponge(Message: buffer, CapacityBits: number, OutputBytes: number, DomainSeparator: number): (string, buffer)
	local RateBytes = (1600 - CapacityBits) // 8
	buffer.fill(LANES_LOW, 0, 0, 100)
	buffer.fill(LANES_HIGH, 0, 0, 100)

	local LanesLow = LANES_LOW
	local LanesHigh = LANES_HIGH

	local MessageLength: number = buffer.len(Message)
	local PaddedLength: number = MessageLength + 1

	local Remainder = PaddedLength % RateBytes
	if Remainder ~= 0 then
		PaddedLength += (RateBytes - Remainder)
	end

	local PaddedMessage = buffer.create(PaddedLength)

	if MessageLength > 0 then
		buffer.copy(PaddedMessage, 0, Message, 0, MessageLength)
	end

	if PaddedLength - MessageLength == 1 then
		buffer.writeu8(PaddedMessage, MessageLength, bit32.bor(DomainSeparator, 0x80))
	else
		buffer.writeu8(PaddedMessage, MessageLength, DomainSeparator)
		if PaddedLength - MessageLength > 2 then
			buffer.fill(PaddedMessage, MessageLength + 1, 0, PaddedLength - MessageLength - 2)
		end
		buffer.writeu8(PaddedMessage, PaddedLength - 1, 0x80)
	end

	Keccak(LanesLow, LanesHigh, PaddedMessage, 0, PaddedLength, RateBytes)

	local Output = buffer.create(OutputBytes)
	local Length = buffer.len(Output)
	local Hex = buffer.create(Length * 2)

	local Lookup = ENCODE_LOOKUP

	local Leftover = Length % 8
	local HexCursor = 0
	local OutputOffset = 0

	local ZeroBuffer = buffer.create(RateBytes)
	while OutputOffset < OutputBytes do
		local BytesThisRound = math.min(RateBytes, OutputBytes - OutputOffset)

		for ByteIndex = 0, BytesThisRound - 1 do
			local AbsoluteIndex = OutputOffset + ByteIndex
			if AbsoluteIndex < OutputBytes then
				local Lane = ByteIndex // 8
				local ByteInLane = ByteIndex % 8
				local LaneOffset = Lane * 4

				local Value
				if ByteInLane < 4 then
					Value = bit32.extract(buffer.readu32(LanesLow, LaneOffset), ByteInLane * 8, 8)
				else
					Value = bit32.extract(buffer.readu32(LanesHigh, LaneOffset), (ByteInLane - 4) * 8, 8)
				end
				buffer.writeu8(Output, AbsoluteIndex, Value)
			end
		end

		OutputOffset += BytesThisRound

		if OutputOffset < OutputBytes then
			Keccak(LanesLow, LanesHigh, ZeroBuffer, 0, RateBytes, RateBytes)
		end
	end

	for Index = 0, Length - Leftover - 1, 8 do
		local Hex1 = buffer.readu16(Lookup, buffer.readu8(Output, Index) * 2)
		local Hex2 = buffer.readu16(Lookup, buffer.readu8(Output, Index + 1) * 2)
		local Hex3 = buffer.readu16(Lookup, buffer.readu8(Output, Index + 2) * 2)
		local Hex4 = buffer.readu16(Lookup, buffer.readu8(Output, Index + 3) * 2)
		local Hex5 = buffer.readu16(Lookup, buffer.readu8(Output, Index + 4) * 2)
		local Hex6 = buffer.readu16(Lookup, buffer.readu8(Output, Index + 5) * 2)
		local Hex7 = buffer.readu16(Lookup, buffer.readu8(Output, Index + 6) * 2)
		local Hex8 = buffer.readu16(Lookup, buffer.readu8(Output, Index + 7) * 2)

		buffer.writeu16(Hex, HexCursor, Hex1)
		buffer.writeu16(Hex, HexCursor + 2, Hex2)
		buffer.writeu16(Hex, HexCursor + 4, Hex3)
		buffer.writeu16(Hex, HexCursor + 6, Hex4)
		buffer.writeu16(Hex, HexCursor + 8, Hex5)
		buffer.writeu16(Hex, HexCursor + 10, Hex6)
		buffer.writeu16(Hex, HexCursor + 12, Hex7)
		buffer.writeu16(Hex, HexCursor + 14, Hex8)

		HexCursor += 16
	end

	for Index = Length - Leftover, Length - 1 do
		local HexPair = buffer.readu16(Lookup, buffer.readu8(Output, Index) * 2)
		buffer.writeu16(Hex, HexCursor, HexPair)
		HexCursor += 2
	end

	return buffer.tostring(Hex), Output
end

function SHA3.SHA3_224(Message: buffer): (string, buffer)
	return ProcessSponge(Message, 448, 28, 0x06)
end

function SHA3.SHA3_256(Message: buffer): (string, buffer)
	return ProcessSponge(Message, 512, 32, 0x06)
end

function SHA3.SHA3_384(Message: buffer): (string, buffer)
	return ProcessSponge(Message, 768, 48, 0x06)
end

function SHA3.SHA3_512(Message: buffer): (string, buffer)
	return ProcessSponge(Message, 1024, 64, 0x06)
end

function SHA3.SHAKE128(Message: buffer, OutputBytes: number): (string, buffer)
	return ProcessSponge(Message, 256, OutputBytes, 0x1F)
end

function SHA3.SHAKE256(Message: buffer, OutputBytes: number): (string, buffer)
	return ProcessSponge(Message, 512, OutputBytes, 0x1F)
end

return SHA3


--[=[
	Cryptography library: XXHash32

	⚠ WARNING: XXHash32 wasn't designed with cryptographic security in mind!
	Only use for non-security purposes. For security, use SHA256 or higher. ⚠

	Return type: number
	Example usage:
		local Message = buffer.fromstring("Hello World")

		--------Usage Case 1--------
		local Result = XXHash32(Message)

		--------Usage Case 2--------
		local Seed = 0xDEADBEEF
		local Result = XXHash32(Message, Seed)
--]=]

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

local function XXH32(Message: buffer, Seed: number?): number
	local PRIME_1, PRIME_1_HIGH, PRIME_1_LOW = 0x9e3779B1, 40503, 31153
	local PRIME_2, PRIME_2_HIGH, PRIME_2_LOW = 0x85ebca77, 34283, 51831
	local PRIME_3_HIGH, PRIME_3_LOW = 49842, 44605
	local PRIME_4_HIGH, PRIME_4_LOW = 10196, 60207
	local PRIME_5, PRIME_5_HIGH, PRIME_5_LOW = 0x165667b1, 5718, 26545

	local UsedSeed = Seed or 0
	local MessageLength = buffer.len(Message)
	local Digest: number
	local CurrentOffset = 0

	if MessageLength >= 16 then
		local Accumulator1 = UsedSeed + PRIME_1 + PRIME_2
		local Accumulator2 = UsedSeed + PRIME_2
		local Accumulator3 = UsedSeed
		local Accumulator4 = UsedSeed - PRIME_1

		while CurrentOffset <= MessageLength - 16 do
			local Word1 = buffer.readu32(Message, CurrentOffset)
			local Word2 = buffer.readu32(Message, CurrentOffset + 4)
			local Word3 = buffer.readu32(Message, CurrentOffset + 8)
			local Word4 = buffer.readu32(Message, CurrentOffset + 12)

			local AHigh1, ALow1 = bit32.rshift(Word1, 16), bit32.band(Word1, 65535)
			local Mult1 = bit32.lshift((AHigh1 * PRIME_2_LOW) + (ALow1 * PRIME_2_HIGH), 16) + (ALow1 * PRIME_2_LOW)

			local Temp1 = bit32.lrotate(Accumulator1 + Mult1, 13)
			local AHigh1_2, ALow1_2 = bit32.rshift(Temp1, 16), bit32.band(Temp1, 65535)
			Accumulator1 = bit32.lshift((AHigh1_2 * PRIME_1_LOW) + (ALow1_2 * PRIME_1_HIGH), 16) + (ALow1_2 * PRIME_1_LOW)

			local AHigh2, ALow2 = bit32.rshift(Word2, 16), bit32.band(Word2, 65535)
			local Mult2 = bit32.lshift((AHigh2 * PRIME_2_LOW) + (ALow2 * PRIME_2_HIGH), 16) + (ALow2 * PRIME_2_LOW)

			local Temp2 = bit32.lrotate(Accumulator2 + Mult2, 13)
			local AHigh2_2, ALow2_2 = bit32.rshift(Temp2, 16), bit32.band(Temp2, 65535)
			Accumulator2 = bit32.lshift((AHigh2_2 * PRIME_1_LOW) + (ALow2_2 * PRIME_1_HIGH), 16) + (ALow2_2 * PRIME_1_LOW)

			local AHigh3, ALow3 = bit32.rshift(Word3, 16), bit32.band(Word3, 65535)
			local Mult3 = bit32.lshift((AHigh3 * PRIME_2_LOW) + (ALow3 * PRIME_2_HIGH), 16) + (ALow3 * PRIME_2_LOW)

			local Temp3 = bit32.lrotate(Accumulator3 + Mult3, 13)
			local AHigh3_2, ALow3_2 = bit32.rshift(Temp3, 16), bit32.band(Temp3, 65535)
			Accumulator3 = bit32.lshift((AHigh3_2 * PRIME_1_LOW) + (ALow3_2 * PRIME_1_HIGH), 16) + (ALow3_2 * PRIME_1_LOW)

			local AHigh4, ALow4 = bit32.rshift(Word4, 16), bit32.band(Word4, 65535)
			local Mult4 = bit32.lshift((AHigh4 * PRIME_2_LOW) + (ALow4 * PRIME_2_HIGH), 16) + (ALow4 * PRIME_2_LOW)

			local Temp4 = bit32.lrotate(Accumulator4 + Mult4, 13)
			local AHigh4_2, ALow4_2 = bit32.rshift(Temp4, 16), bit32.band(Temp4, 65535)
			Accumulator4 = bit32.lshift((AHigh4_2 * PRIME_1_LOW) + (ALow4_2 * PRIME_1_HIGH), 16) + (ALow4_2 * PRIME_1_LOW)

			CurrentOffset += 16
		end

		Digest = bit32.lrotate(Accumulator1, 1) + bit32.lrotate(Accumulator2, 7) + bit32.lrotate(Accumulator3, 12) + bit32.lrotate(Accumulator4, 18)
	else
		Digest = UsedSeed + PRIME_5
	end

	Digest += MessageLength

	while CurrentOffset <= MessageLength - 4 do
		if CurrentOffset + 4 <= buffer.len(Message) then
			local Word = buffer.readu32(Message, CurrentOffset)

			local AHigh_w, ALow_w = bit32.rshift(Word, 16), bit32.band(Word, 65535)
			local Mult_w = bit32.lshift((AHigh_w * PRIME_3_LOW) + (ALow_w * PRIME_3_HIGH), 16) + (ALow_w * PRIME_3_LOW)

			Digest += Mult_w

			local Rotated = bit32.lrotate(Digest, 17)
			local AHigh_r, ALow_r = bit32.rshift(Rotated, 16), bit32.band(Rotated, 65535)
			Digest = bit32.lshift((AHigh_r * PRIME_4_LOW) + (ALow_r * PRIME_4_HIGH), 16) + (ALow_r * PRIME_4_LOW)
		end
		CurrentOffset += 4
	end

	while CurrentOffset < MessageLength do
		if CurrentOffset < buffer.len(Message) then
			local ByteValue = buffer.readu8(Message, CurrentOffset)

			local AHigh_b, ALow_b = bit32.rshift(ByteValue, 16), bit32.band(ByteValue, 65535)
			local Mult_b = bit32.lshift((AHigh_b * PRIME_5_LOW) + (ALow_b * PRIME_5_HIGH), 16) + (ALow_b * PRIME_5_LOW)

			Digest += Mult_b

			local Rotated_b = bit32.lrotate(Digest, 11)
			local AHigh_rb, ALow_rb = bit32.rshift(Rotated_b, 16), bit32.band(Rotated_b, 65535)
			Digest = bit32.lshift((AHigh_rb * PRIME_1_LOW) + (ALow_rb * PRIME_1_HIGH), 16) + (ALow_rb * PRIME_1_LOW)
		end
		CurrentOffset += 1
	end

	local XorResult1 = bit32.bxor(Digest, bit32.rshift(Digest, 15))
	local AHigh_f1, ALow_f1 = bit32.rshift(XorResult1, 16), bit32.band(XorResult1, 65535)
	Digest = bit32.lshift((AHigh_f1 * PRIME_2_LOW) + (ALow_f1 * PRIME_2_HIGH), 16) + (ALow_f1 * PRIME_2_LOW)

	local XorResult2 = bit32.bxor(Digest, bit32.rshift(Digest, 13))
	local AHigh_f2, ALow_f2 = bit32.rshift(XorResult2, 16), bit32.band(XorResult2, 65535)
	Digest = bit32.lshift((AHigh_f2 * PRIME_3_LOW) + (ALow_f2 * PRIME_3_HIGH), 16) + (ALow_f2 * PRIME_3_LOW)

	return bit32.bxor(Digest, bit32.rshift(Digest, 16))
end

return XXH32


--!strict

local Algorithms = table.freeze({
	HMAC = require("@self/HMAC"),
	HKDF = require("@self/HKDF"),
	KMAC = require("@self/KMAC"),
	MD5 = require("@self/MD5"),
	SHA1 = require("@self/SHA1"),
	SHA2 = require("@self/SHA2"),
	SHA3 = require("@self/SHA3"),
	XXH32 = require("@self/XXH32"),
	Blake2b = require("@self/Blake2b"),
	Blake3 = require("@self/Blake3"),
	MurMur = require("@self/Murmur")
})

return Algorithms