--[=[
	Bit packing and unpacking utilities

	Serializes polynomials to byte arrays with various bit widths.
	Supports encoding/decoding of polynomial coefficients and hint bits.

	Example usage:
		local BitPacking = require(script)

		local Poly = buffer.create(256 * 4)
		local Encoded = buffer.create(96)  -- For 3-bit encoding
		BitPacking.Encode(Poly, Encoded, 3)
		BitPacking.Decode(Encoded, Poly, 3)
--]=]

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

local BitPacking = {}

function BitPacking.Encode(Poly: buffer, Arr: buffer, Sbw: number)
	if buffer.len(Poly) ~= 256 * 4 then
		error("Invalid poly size")
	end

	if buffer.len(Arr) ~= math.floor(256 * Sbw / 8) then
		error("Invalid arr size")
	end

	buffer.fill(Arr, 0, 0, buffer.len(Arr))

	if Sbw == 3 then
		local ItrCnt = bit32.rshift(256, 3)

		for I = 0, ItrCnt - 1 do
			local POffset = bit32.lshift(I, 3)
			local BOffset = I * 3

			local P0 = bit32.band(buffer.readu32(Poly, (POffset + 0) * 4), 0x7)
			local P1 = bit32.band(buffer.readu32(Poly, (POffset + 1) * 4), 0x7)
			local P2 = bit32.band(buffer.readu32(Poly, (POffset + 2) * 4), 0x7)
			local P3 = bit32.band(buffer.readu32(Poly, (POffset + 3) * 4), 0x7)
			local P4 = bit32.band(buffer.readu32(Poly, (POffset + 4) * 4), 0x7)
			local P5 = bit32.band(buffer.readu32(Poly, (POffset + 5) * 4), 0x7)
			local P6 = bit32.band(buffer.readu32(Poly, (POffset + 6) * 4), 0x7)
			local P7 = bit32.band(buffer.readu32(Poly, (POffset + 7) * 4), 0x7)

			buffer.writeu8(Arr, BOffset + 0, bit32.bor(bit32.lshift(bit32.band(P2, 0x3), 6), bit32.lshift(P1, 3), P0))

			buffer.writeu8(
				Arr,
				BOffset + 1,
				bit32.bor(
					bit32.lshift(bit32.band(P5, 0x1), 7),
					bit32.lshift(P4, 4),
					bit32.lshift(P3, 1),
					bit32.rshift(P2, 2)
				)
			)

			buffer.writeu8(Arr, BOffset + 2, bit32.bor(bit32.lshift(P7, 5), bit32.lshift(P6, 2), bit32.rshift(P5, 1)))
		end
	elseif Sbw == 4 then
		local ItrCnt = bit32.rshift(256, 1)

		for I = 0, ItrCnt - 1 do
			local Off = bit32.lshift(I, 1)
			local P0 = bit32.band(buffer.readu32(Poly, (Off + 0) * 4), 0xF)
			local P1 = bit32.band(buffer.readu32(Poly, (Off + 1) * 4), 0xF)

			buffer.writeu8(Arr, I, bit32.bor(bit32.lshift(P1, 4), P0))
		end
	elseif Sbw == 6 then
		local ItrCnt = bit32.rshift(256, 2)

		for I = 0, ItrCnt - 1 do
			local POffset = bit32.lshift(I, 2)
			local BOffset = I * 3

			local P0 = bit32.band(buffer.readu32(Poly, (POffset + 0) * 4), 0x3F)
			local P1 = bit32.band(buffer.readu32(Poly, (POffset + 1) * 4), 0x3F)
			local P2 = bit32.band(buffer.readu32(Poly, (POffset + 2) * 4), 0x3F)
			local P3 = bit32.band(buffer.readu32(Poly, (POffset + 3) * 4), 0x3F)

			buffer.writeu8(Arr, BOffset + 0, bit32.bor(bit32.lshift(bit32.band(P1, 0x3), 6), P0))
			buffer.writeu8(Arr, BOffset + 1, bit32.bor(bit32.lshift(bit32.band(P2, 0xF), 4), bit32.rshift(P1, 2)))
			buffer.writeu8(Arr, BOffset + 2, bit32.bor(bit32.lshift(P3, 2), bit32.rshift(P2, 4)))
		end
	elseif Sbw == 10 then
		local ItrCnt = bit32.rshift(256, 2)

		for I = 0, ItrCnt - 1 do
			local POffset = I * 4
			local BOffset = I * 5

			local P0 = bit32.band(buffer.readu32(Poly, POffset * 4), 0x3FF)
			local P1 = bit32.band(buffer.readu32(Poly, (POffset + 1) * 4), 0x3FF)
			local P2 = bit32.band(buffer.readu32(Poly, (POffset + 2) * 4), 0x3FF)
			local P3 = bit32.band(buffer.readu32(Poly, (POffset + 3) * 4), 0x3FF)

			buffer.writeu8(Arr, BOffset + 0, bit32.band(P0, 0xFF))
			buffer.writeu8(Arr, BOffset + 1, bit32.bor(bit32.lshift(bit32.band(P1, 0x3F), 2), bit32.rshift(P0, 8)))
			buffer.writeu8(Arr, BOffset + 2, bit32.bor(bit32.lshift(bit32.band(P2, 0xF), 4), bit32.rshift(P1, 6)))
			buffer.writeu8(Arr, BOffset + 3, bit32.bor(bit32.lshift(bit32.band(P3, 0x3), 6), bit32.rshift(P2, 4)))
			buffer.writeu8(Arr, BOffset + 4, bit32.rshift(P3, 2))
		end
	elseif Sbw == 13 then
		local ItrCnt = bit32.rshift(256, 3)

		for I = 0, ItrCnt - 1 do
			local POffset = bit32.lshift(I, 3)
			local BOffset = I * 13

			local P0 = bit32.band(buffer.readu32(Poly, (POffset + 0) * 4), 0x1FFF)
			local P1 = bit32.band(buffer.readu32(Poly, (POffset + 1) * 4), 0x1FFF)
			local P2 = bit32.band(buffer.readu32(Poly, (POffset + 2) * 4), 0x1FFF)
			local P3 = bit32.band(buffer.readu32(Poly, (POffset + 3) * 4), 0x1FFF)
			local P4 = bit32.band(buffer.readu32(Poly, (POffset + 4) * 4), 0x1FFF)
			local P5 = bit32.band(buffer.readu32(Poly, (POffset + 5) * 4), 0x1FFF)
			local P6 = bit32.band(buffer.readu32(Poly, (POffset + 6) * 4), 0x1FFF)
			local P7 = bit32.band(buffer.readu32(Poly, (POffset + 7) * 4), 0x1FFF)

			buffer.writeu8(Arr, BOffset + 0, bit32.band(P0, 0xFF))
			buffer.writeu8(Arr, BOffset + 1, bit32.bor(bit32.lshift(bit32.band(P1, 0x7), 5), bit32.rshift(P0, 8)))
			buffer.writeu8(Arr, BOffset + 2, bit32.rshift(P1, 3))
			buffer.writeu8(Arr, BOffset + 3, bit32.bor(bit32.lshift(bit32.band(P2, 0x3F), 2), bit32.rshift(P1, 11)))
			buffer.writeu8(Arr, BOffset + 4, bit32.bor(bit32.lshift(bit32.band(P3, 0x1), 7), bit32.rshift(P2, 6)))
			buffer.writeu8(Arr, BOffset + 5, bit32.rshift(P3, 1))
			buffer.writeu8(Arr, BOffset + 6, bit32.bor(bit32.lshift(bit32.band(P4, 0xF), 4), bit32.rshift(P3, 9)))
			buffer.writeu8(Arr, BOffset + 7, bit32.rshift(P4, 4))
			buffer.writeu8(Arr, BOffset + 8, bit32.bor(bit32.lshift(bit32.band(P5, 0x7F), 1), bit32.rshift(P4, 12)))
			buffer.writeu8(Arr, BOffset + 9, bit32.bor(bit32.lshift(bit32.band(P6, 0x3), 6), bit32.rshift(P5, 7)))
			buffer.writeu8(Arr, BOffset + 10, bit32.rshift(P6, 2))
			buffer.writeu8(Arr, BOffset + 11, bit32.bor(bit32.lshift(bit32.band(P7, 0x1F), 3), bit32.rshift(P6, 10)))
			buffer.writeu8(Arr, BOffset + 12, bit32.rshift(P7, 5))
		end
	elseif Sbw == 18 then
		local ItrCnt = bit32.rshift(256, 2)

		for I = 0, ItrCnt - 1 do
			local POffset = bit32.lshift(I, 2)
			local BOffset = I * 9

			local P0 = bit32.band(buffer.readu32(Poly, (POffset + 0) * 4), 0x3FFFF)
			local P1 = bit32.band(buffer.readu32(Poly, (POffset + 1) * 4), 0x3FFFF)
			local P2 = bit32.band(buffer.readu32(Poly, (POffset + 2) * 4), 0x3FFFF)
			local P3 = bit32.band(buffer.readu32(Poly, (POffset + 3) * 4), 0x3FFFF)

			buffer.writeu8(Arr, BOffset + 0, bit32.band(P0, 0xFF))
			buffer.writeu8(Arr, BOffset + 1, bit32.band(bit32.rshift(P0, 8), 0xFF))
			buffer.writeu8(Arr, BOffset + 2, bit32.bor(bit32.lshift(bit32.band(P1, 0x3F), 2), bit32.rshift(P0, 16)))
			buffer.writeu8(Arr, BOffset + 3, bit32.band(bit32.rshift(P1, 6), 0xFF))
			buffer.writeu8(Arr, BOffset + 4, bit32.bor(bit32.lshift(bit32.band(P2, 0xF), 4), bit32.rshift(P1, 14)))
			buffer.writeu8(Arr, BOffset + 5, bit32.band(bit32.rshift(P2, 4), 0xFF))
			buffer.writeu8(Arr, BOffset + 6, bit32.bor(bit32.lshift(bit32.band(P3, 0x3), 6), bit32.rshift(P2, 12)))
			buffer.writeu8(Arr, BOffset + 7, bit32.band(bit32.rshift(P3, 2), 0xFF))
			buffer.writeu8(Arr, BOffset + 8, bit32.rshift(P3, 10))
		end
	elseif Sbw == 20 then
		local ItrCnt = bit32.rshift(256, 2)

		for I = 0, ItrCnt - 1 do
			local POffset = bit32.lshift(I, 2)
			local BOffset = I * 10

			local P0 = bit32.band(buffer.readu32(Poly, (POffset + 0) * 4), 0xFFFFF)
			local P1 = bit32.band(buffer.readu32(Poly, (POffset + 1) * 4), 0xFFFFF)
			local P2 = bit32.band(buffer.readu32(Poly, (POffset + 2) * 4), 0xFFFFF)
			local P3 = bit32.band(buffer.readu32(Poly, (POffset + 3) * 4), 0xFFFFF)

			buffer.writeu8(Arr, BOffset + 0, bit32.band(P0, 0xFF))
			buffer.writeu8(Arr, BOffset + 1, bit32.band(bit32.rshift(P0, 8), 0xFF))
			buffer.writeu8(Arr, BOffset + 2, bit32.bor(bit32.lshift(bit32.band(P1, 0xF), 4), bit32.rshift(P0, 16)))
			buffer.writeu8(Arr, BOffset + 3, bit32.band(bit32.rshift(P1, 4), 0xFF))
			buffer.writeu8(Arr, BOffset + 4, bit32.rshift(P1, 12))
			buffer.writeu8(Arr, BOffset + 5, bit32.band(P2, 0xFF))
			buffer.writeu8(Arr, BOffset + 6, bit32.band(bit32.rshift(P2, 8), 0xFF))
			buffer.writeu8(Arr, BOffset + 7, bit32.bor(bit32.lshift(bit32.band(P3, 0xF), 4), bit32.rshift(P2, 16)))
			buffer.writeu8(Arr, BOffset + 8, bit32.band(bit32.rshift(P3, 4), 0xFF))
			buffer.writeu8(Arr, BOffset + 9, bit32.rshift(P3, 12))
		end
	else
		local Mask = bit32.lshift(1, Sbw) - 1
		for I = 0, buffer.len(Arr) * 8 - 1 do
			local PIdx = math.floor(I / Sbw)
			local POffset = I % Sbw

			local AIdx = bit32.rshift(I, 3)
			local AOffset = bit32.band(I, 7)

			if PIdx < 256 then
				local PolyValue = bit32.band(buffer.readu32(Poly, PIdx * 4), Mask)
				local Bit = bit32.band(bit32.rshift(PolyValue, POffset), 1)
				local CurrentByte = buffer.readu8(Arr, AIdx)
				buffer.writeu8(Arr, AIdx, bit32.bxor(CurrentByte, bit32.lshift(Bit, AOffset)))
			end
		end
	end
end

function BitPacking.Decode(Arr: buffer, Poly: buffer, Sbw: number)
	buffer.fill(Poly, 0, 0, 256 * 4)

	if Sbw == 3 then
		local ItrCnt = bit32.rshift(256, 3)

		for I = 0, ItrCnt - 1 do
			local POffset = bit32.lshift(I, 3)
			local BOffset = I * 3

			local B0 = buffer.readu8(Arr, BOffset + 0)
			local B1 = buffer.readu8(Arr, BOffset + 1)
			local B2 = buffer.readu8(Arr, BOffset + 2)

			buffer.writeu32(Poly, (POffset + 0) * 4, bit32.band(B0, 0x7))
			buffer.writeu32(Poly, (POffset + 1) * 4, bit32.band(bit32.rshift(B0, 3), 0x7))
			buffer.writeu32(
				Poly,
				(POffset + 2) * 4,
				bit32.bor(bit32.lshift(bit32.band(B1, 0x1), 2), bit32.rshift(B0, 6))
			)
			buffer.writeu32(Poly, (POffset + 3) * 4, bit32.band(bit32.rshift(B1, 1), 0x7))
			buffer.writeu32(Poly, (POffset + 4) * 4, bit32.band(bit32.rshift(B1, 4), 0x7))
			buffer.writeu32(
				Poly,
				(POffset + 5) * 4,
				bit32.bor(bit32.lshift(bit32.band(B2, 0x3), 1), bit32.rshift(B1, 7))
			)
			buffer.writeu32(Poly, (POffset + 6) * 4, bit32.band(bit32.rshift(B2, 2), 0x7))
			buffer.writeu32(Poly, (POffset + 7) * 4, bit32.rshift(B2, 5))
		end
	elseif Sbw == 4 then
		local ItrCnt = bit32.rshift(256, 1)

		for I = 0, ItrCnt - 1 do
			local Off = bit32.lshift(I, 1)
			local Byte = buffer.readu8(Arr, I)

			buffer.writeu32(Poly, (Off + 0) * 4, bit32.band(Byte, 0xF))
			buffer.writeu32(Poly, (Off + 1) * 4, bit32.rshift(Byte, 4))
		end
	elseif Sbw == 6 then
		local ItrCnt = bit32.rshift(256, 2)

		for I = 0, ItrCnt - 1 do
			local POffset = bit32.lshift(I, 2)
			local BOffset = I * 3

			local B0 = buffer.readu8(Arr, BOffset + 0)
			local B1 = buffer.readu8(Arr, BOffset + 1)
			local B2 = buffer.readu8(Arr, BOffset + 2)

			buffer.writeu32(Poly, (POffset + 0) * 4, bit32.band(B0, 0x3F))
			buffer.writeu32(
				Poly,
				(POffset + 1) * 4,
				bit32.bor(bit32.lshift(bit32.band(B1, 0xF), 2), bit32.rshift(B0, 6))
			)
			buffer.writeu32(
				Poly,
				(POffset + 2) * 4,
				bit32.bor(bit32.lshift(bit32.band(B2, 0x3), 4), bit32.rshift(B1, 4))
			)
			buffer.writeu32(Poly, (POffset + 3) * 4, bit32.rshift(B2, 2))
		end
	elseif Sbw == 10 then
		local ItrCnt = bit32.rshift(256, 2)

		for I = 0, ItrCnt - 1 do
			local POffset = I * 4
			local BOffset = I * 5

			local B0 = buffer.readu8(Arr, BOffset + 0)
			local B1 = buffer.readu8(Arr, BOffset + 1)
			local B2 = buffer.readu8(Arr, BOffset + 2)
			local B3 = buffer.readu8(Arr, BOffset + 3)
			local B4 = buffer.readu8(Arr, BOffset + 4)

			buffer.writeu32(Poly, POffset * 4, bit32.bor(bit32.lshift(bit32.band(B1, 0x3), 8), B0))
			buffer.writeu32(
				Poly,
				(POffset + 1) * 4,
				bit32.bor(bit32.lshift(bit32.band(B2, 0xF), 6), bit32.rshift(B1, 2))
			)
			buffer.writeu32(
				Poly,
				(POffset + 2) * 4,
				bit32.bor(bit32.lshift(bit32.band(B3, 0x3F), 4), bit32.rshift(B2, 4))
			)
			buffer.writeu32(Poly, (POffset + 3) * 4, bit32.bor(bit32.lshift(B4, 2), bit32.rshift(B3, 6)))
		end
	elseif Sbw == 13 then
		local ItrCnt = bit32.rshift(256, 3)

		for I = 0, ItrCnt - 1 do
			local POffset = bit32.lshift(I, 3)
			local BOffset = I * 13

			local B0 = buffer.readu8(Arr, BOffset + 0)
			local B1 = buffer.readu8(Arr, BOffset + 1)
			local B2 = buffer.readu8(Arr, BOffset + 2)
			local B3 = buffer.readu8(Arr, BOffset + 3)
			local B4 = buffer.readu8(Arr, BOffset + 4)
			local B5 = buffer.readu8(Arr, BOffset + 5)
			local B6 = buffer.readu8(Arr, BOffset + 6)
			local B7 = buffer.readu8(Arr, BOffset + 7)
			local B8 = buffer.readu8(Arr, BOffset + 8)
			local B9 = buffer.readu8(Arr, BOffset + 9)
			local B10 = buffer.readu8(Arr, BOffset + 10)
			local B11 = buffer.readu8(Arr, BOffset + 11)
			local B12 = buffer.readu8(Arr, BOffset + 12)

			buffer.writeu32(Poly, (POffset + 0) * 4, bit32.bor(bit32.lshift(bit32.band(B1, 0x1F), 8), B0))
			buffer.writeu32(
				Poly,
				(POffset + 1) * 4,
				bit32.bor(bit32.lshift(bit32.band(B3, 0x3), 11), bit32.lshift(B2, 3), bit32.rshift(B1, 5))
			)
			buffer.writeu32(
				Poly,
				(POffset + 2) * 4,
				bit32.bor(bit32.lshift(bit32.band(B4, 0x7F), 6), bit32.rshift(B3, 2))
			)
			buffer.writeu32(
				Poly,
				(POffset + 3) * 4,
				bit32.bor(bit32.lshift(bit32.band(B6, 0xF), 9), bit32.lshift(B5, 1), bit32.rshift(B4, 7))
			)
			buffer.writeu32(
				Poly,
				(POffset + 4) * 4,
				bit32.bor(bit32.lshift(bit32.band(B8, 0x1), 12), bit32.lshift(B7, 4), bit32.rshift(B6, 4))
			)
			buffer.writeu32(
				Poly,
				(POffset + 5) * 4,
				bit32.bor(bit32.lshift(bit32.band(B9, 0x3F), 7), bit32.rshift(B8, 1))
			)
			buffer.writeu32(
				Poly,
				(POffset + 6) * 4,
				bit32.bor(bit32.lshift(bit32.band(B11, 0x7), 10), bit32.lshift(B10, 2), bit32.rshift(B9, 6))
			)
			buffer.writeu32(Poly, (POffset + 7) * 4, bit32.bor(bit32.lshift(B12, 5), bit32.rshift(B11, 3)))
		end
	elseif Sbw == 18 then
		local ItrCnt = bit32.rshift(256, 2)

		for I = 0, ItrCnt - 1 do
			local POffset = bit32.lshift(I, 2)
			local BOffset = I * 9

			local B0 = buffer.readu8(Arr, BOffset + 0)
			local B1 = buffer.readu8(Arr, BOffset + 1)
			local B2 = buffer.readu8(Arr, BOffset + 2)
			local B3 = buffer.readu8(Arr, BOffset + 3)
			local B4 = buffer.readu8(Arr, BOffset + 4)
			local B5 = buffer.readu8(Arr, BOffset + 5)
			local B6 = buffer.readu8(Arr, BOffset + 6)
			local B7 = buffer.readu8(Arr, BOffset + 7)
			local B8 = buffer.readu8(Arr, BOffset + 8)

			buffer.writeu32(
				Poly,
				(POffset + 0) * 4,
				bit32.bor(bit32.lshift(bit32.band(B2, 0x3), 16), bit32.lshift(B1, 8), B0)
			)
			buffer.writeu32(
				Poly,
				(POffset + 1) * 4,
				bit32.bor(bit32.lshift(bit32.band(B4, 0xF), 14), bit32.lshift(B3, 6), bit32.rshift(B2, 2))
			)
			buffer.writeu32(
				Poly,
				(POffset + 2) * 4,
				bit32.bor(bit32.lshift(bit32.band(B6, 0x3F), 12), bit32.lshift(B5, 4), bit32.rshift(B4, 4))
			)
			buffer.writeu32(
				Poly,
				(POffset + 3) * 4,
				bit32.bor(bit32.lshift(B8, 10), bit32.lshift(B7, 2), bit32.rshift(B6, 6))
			)
		end
	elseif Sbw == 20 then
		local ItrCnt = bit32.rshift(256, 2)

		for I = 0, ItrCnt - 1 do
			local POffset = bit32.lshift(I, 2)
			local BOffset = I * 10

			local B0 = buffer.readu8(Arr, BOffset + 0)
			local B1 = buffer.readu8(Arr, BOffset + 1)
			local B2 = buffer.readu8(Arr, BOffset + 2)
			local B3 = buffer.readu8(Arr, BOffset + 3)
			local B4 = buffer.readu8(Arr, BOffset + 4)
			local B5 = buffer.readu8(Arr, BOffset + 5)
			local B6 = buffer.readu8(Arr, BOffset + 6)
			local B7 = buffer.readu8(Arr, BOffset + 7)
			local B8 = buffer.readu8(Arr, BOffset + 8)
			local B9 = buffer.readu8(Arr, BOffset + 9)

			buffer.writeu32(
				Poly,
				(POffset + 0) * 4,
				bit32.bor(bit32.lshift(bit32.band(B2, 0xF), 16), bit32.lshift(B1, 8), B0)
			)
			buffer.writeu32(
				Poly,
				(POffset + 1) * 4,
				bit32.bor(bit32.lshift(B4, 12), bit32.lshift(B3, 4), bit32.rshift(B2, 4))
			)
			buffer.writeu32(
				Poly,
				(POffset + 2) * 4,
				bit32.bor(bit32.lshift(bit32.band(B7, 0xF), 16), bit32.lshift(B6, 8), B5)
			)
			buffer.writeu32(
				Poly,
				(POffset + 3) * 4,
				bit32.bor(bit32.lshift(B9, 12), bit32.lshift(B8, 4), bit32.rshift(B7, 4))
			)
		end
	else
		for I = 0, buffer.len(Arr) * 8 - 1 do
			local AIdx = bit32.rshift(I, 3)
			local AOffset = bit32.band(I, 7)

			local PIdx = math.floor(I / Sbw)
			local POffset = I % Sbw

			if PIdx < 256 then
				local Bit = bit32.band(bit32.rshift(buffer.readu8(Arr, AIdx), AOffset), 1)
				local CurrentPoly = buffer.readu32(Poly, PIdx * 4)
				buffer.writeu32(Poly, PIdx * 4, bit32.bxor(CurrentPoly, bit32.lshift(Bit, POffset)))
			end
		end
	end
end

function BitPacking.EncodeHintBits(H: buffer, Arr: buffer, K: number, Omega: number)
	buffer.fill(Arr, 0, 0, buffer.len(Arr))

	local Idx = 0

	for I = 0, K - 1 do
		local Off = I * 256

		for J = 0, 256 - 1 do
			local HValue = buffer.readu32(H, (Off + J) * 4)
			if HValue ~= 0 then
				buffer.writeu8(Arr, Idx, J)
				Idx += 1
			end
		end

		buffer.writeu8(Arr, Omega + I, Idx)
	end
end

function BitPacking.DecodeHintBits(Arr: buffer, H: buffer, K: number, Omega: number): boolean
	buffer.fill(H, 0, 0, K * 256 * 4)

	local Idx = 0
	local Failed = false

	for I = 0, K - 1 do
		local Off = I * 256
		local Till = buffer.readu8(Arr, Omega + I)

		if Till < Idx or Till > Omega then
			Failed = true
		end

		for J = Idx, Till - 1 do
			if Failed then
				break
			end

			if J > Idx then
				local CurrentJ = buffer.readu8(Arr, J)
				local PrevJ = buffer.readu8(Arr, J - 1)
				if CurrentJ <= PrevJ then
					Failed = true
				end
			end

			if not Failed then
				local Position = buffer.readu8(Arr, J)
				if Position >= 256 then
					Failed = true
				end

				if not Failed then
					buffer.writeu32(H, (Off + Position) * 4, 1)
				end
			end
		end

		Idx = Till
	end

	for I = Idx, Omega - 1 do
		if buffer.readu8(Arr, I) ~= 0 then
			Failed = true
		end
	end

	return Failed
end

return BitPacking


--[=[
	Parameter validation for ML-DSA algorithms

	FIPS 204 specification.

	Example usage:
		local Params = require(script)

		local IsValid = Params.CheckKeygenParams(4, 4, 13, 2)  -- ML-DSA-44
		local ValidEta = Params.CheckEta(2)
--]=]

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

local Q_BIT_WIDTH = 23
local Q = 8380417

local Params = {}

function Params.CheckEta(Eta: number): boolean
	return Eta == 2 or Eta == 4
end

function Params.CheckNonce(Nonce: number): boolean
	return Nonce == 0 or Nonce == 4 or Nonce == 5 or Nonce == 7
end

function Params.CheckGamma1(Gamma1: number): boolean
	return Gamma1 == bit32.lshift(1, 17) or Gamma1 == bit32.lshift(1, 19)
end

function Params.CheckGamma2(Gamma2: number): boolean
	return Gamma2 == math.floor((Q - 1) / 88) or Gamma2 == math.floor((Q - 1) / 32)
end

function Params.CheckTau(Tau: number): boolean
	return Tau == 39 or Tau == 49 or Tau == 60
end

function Params.CheckD(D: number): boolean
	return D == 13
end

function Params.CheckMatrixDim(ACols: number, BRows: number): boolean
	return ACols == BRows
end

function Params.CheckKeygenParams(K: number, L: number, D: number, Eta: number): boolean
	return (K == 4 and L == 4 and D == 13 and Eta == 2)
		or (K == 6 and L == 5 and D == 13 and Eta == 4)
		or (K == 8 and L == 7 and D == 13 and Eta == 2)
end

function Params.CheckSigningParams(
	K: number,
	L: number,
	D: number,
	Eta: number,
	Gamma1: number,
	Gamma2: number,
	Tau: number,
	Beta: number,
	Omega: number,
	Lambda: number
): boolean
	return (
		K == 4
		and L == 4
		and D == 13
		and Eta == 2
		and Gamma1 == bit32.lshift(1, 17)
		and Gamma2 == math.floor((Q - 1) / 88)
		and Tau == 39
		and Beta == Tau * Eta
		and Omega == 80
		and Lambda == 128
	)
		or (K == 6 and L == 5 and D == 13 and Eta == 4 and Gamma1 == bit32.lshift(1, 19) and Gamma2 == math.floor(
			(Q - 1) / 32
		) and Tau == 49 and Beta == Tau * Eta and Omega == 55 and Lambda == 192)
		or (
			K == 8
			and L == 7
			and D == 13
			and Eta == 2
			and Gamma1 == bit32.lshift(1, 19)
			and Gamma2 == math.floor((Q - 1) / 32)
			and Tau == 60
			and Beta == Tau * Eta
			and Omega == 75
			and Lambda == 256
		)
end

function Params.CheckVerifyParams(
	K: number,
	L: number,
	D: number,
	Gamma1: number,
	Gamma2: number,
	Tau: number,
	Beta: number,
	Omega: number,
	Lambda: number
): boolean
	return (
		K == 4
		and L == 4
		and D == 13
		and Gamma1 == bit32.lshift(1, 17)
		and Gamma2 == math.floor((Q - 1) / 88)
		and Tau == 39
		and Beta == Tau * 2
		and Omega == 80
		and Lambda == 128
	)
		or (K == 6 and L == 5 and D == 13 and Gamma1 == bit32.lshift(1, 19) and Gamma2 == math.floor((Q - 1) / 32) and Tau == 49 and Beta == Tau * 4 and Omega == 55 and Lambda == 192)
		or (
			K == 8
			and L == 7
			and D == 13
			and Gamma1 == bit32.lshift(1, 19)
			and Gamma2 == math.floor((Q - 1) / 32)
			and Tau == 60
			and Beta == Tau * 2
			and Omega == 75
			and Lambda == 256
		)
end

return Params


--[=[
	Vector operations on degree-255 polynomials

	Works on vectors of polynomials including matrix multiplication,
	NTT transforms, and encoding/decoding for ML-DSA.

	Example usage:
		local PolyVec = require(script)

		local Vec = buffer.create(4 * 256 * 4)  -- 4 polynomials
		PolyVec.ForwardNTT(Vec, 4)
		local Norm = PolyVec.InfinityNorm(Vec, 4)
--]=]

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

local Field = require("./Field")
local NTT = require("./NTT")
local BitPacking = require("./Pack")

local LOG2N = 8
local ZETA = 1753
local Q = 8380417

local N = 256
local INV_N = Field.Inverse(N)

local ZETA_EXP, ZETA_NEG_EXP = buffer.create(N * 4), buffer.create(N * 4)
do
	for I = 0, N - 1 do
		local Reversed = 0
		local Value = I

		for J = 0, LOG2N - 1 do
			local Bit = bit32.band(bit32.rshift(Value, J), 1)
			Reversed = bit32.bxor(Reversed, bit32.lshift(Bit, LOG2N - 1 - J))
		end

		local ZetaExp = Field.Power(ZETA, Reversed)
		local ZetaNegExp = Field.Negate(ZetaExp)

		buffer.writeu32(ZETA_EXP, I * 4, ZetaExp)
		buffer.writeu32(ZETA_NEG_EXP, I * 4, ZetaNegExp)
	end
end

local PolyVec = {}

function PolyVec.ForwardNTT(Vec: buffer, K: number)
	local Num = N
	for I = 0, K - 1 do
		local Offset = I * Num * 4
		NTT.ForwardNTTWithOffset(Vec, Offset)
	end
end

function PolyVec.InverseNTT(Vec: buffer, K: number)
	local Zeta = ZETA_NEG_EXP
	local InvN = INV_N
	local Num = N
	local Modulus = Q

	for I = 0, K - 1 do
		local Offset = I * Num * 4

		for L = 0, 7 do
			local Len = bit32.lshift(1, L)
			local LenX2 = bit32.lshift(Len, 1)
			local KBeg = bit32.rshift(Num, L) - 1

			for Start = 0, Num - 1, LenX2 do
				local KNow = KBeg - bit32.rshift(Start, L + 1)
				local NegZetaExpValue = buffer.readu32(Zeta, KNow * 4)

				for J = Start, Start + Len - 1 do
					local JOffset = Offset + J * 4
					local JLenOffset = Offset + (J + Len) * 4

					local PolyJ = buffer.readu32(Vec, JOffset)
					local PolyJLen = buffer.readu32(Vec, JLenOffset)

					local Sum = PolyJ + PolyJLen
					Sum = if Sum >= Modulus then Sum - Modulus else Sum

					local Diff = if PolyJ >= PolyJLen then PolyJ - PolyJLen else PolyJ - PolyJLen + Modulus
					local Product = (Diff * NegZetaExpValue) % Modulus

					buffer.writeu32(Vec, JOffset, Sum)
					buffer.writeu32(Vec, JLenOffset, Product)
				end
			end
		end

		for J = 0, Num - 1 do
			local JOffset = Offset + J * 4
			local PolyValue = buffer.readu32(Vec, JOffset)
			local Result = (PolyValue * InvN) % Modulus
			buffer.writeu32(Vec, JOffset, Result)
		end
	end
end

function PolyVec.Power2Round(PolyVec: buffer, PolyHi: buffer, PolyLo: buffer, K: number, D: number)
	local Max = bit32.lshift(1, D - 1)
	local Num = N
	local Modulus = Q

	for I = 0, K - 1 do
		local Offset = I * Num * 4

		for J = 0, Num - 1 do
			local JOffset = Offset + J * 4
			local R = buffer.readu32(PolyVec, JOffset)

			local T1 = R + Max - 1
			local T2 = bit32.rshift(T1, D)
			local T3 = bit32.lshift(T2, D)

			local Hi = T2
			local Lo = if R >= T3 then R - T3 else R - T3 + Modulus

			buffer.writeu32(PolyHi, JOffset, Hi)
			buffer.writeu32(PolyLo, JOffset, Lo)
		end
	end
end

function PolyVec.MatrixMultiply(
	A: buffer,
	B: buffer,
	C: buffer,
	ARows: number,
	ACols: number,
	BRows: number,
	BCols: number
)
	local Num = N
	local Modulus = Q

	buffer.fill(C, 0, 0, ARows * BCols * N * 4)

	for I = 0, ARows - 1 do
		for J = 0, BCols - 1 do
			local COffset = (I * BCols + J) * Num * 4

			for K = 0, ACols - 1 do
				local AOffset = (I * ACols + K) * Num * 4
				local BOffset = (K * BCols + J) * Num * 4

				for L = 0, Num - 1 do
					local ElementOffset = L * 4
					local AValue = buffer.readu32(A, AOffset + ElementOffset)
					local BValue = buffer.readu32(B, BOffset + ElementOffset)

					local Product = (AValue * BValue) % Modulus

					local CValue = buffer.readu32(C, COffset + ElementOffset)
					local Sum = CValue + Product
					Sum = if Sum >= Modulus then Sum - Modulus else Sum

					buffer.writeu32(C, COffset + ElementOffset, Sum)
				end
			end
		end
	end
end

function PolyVec.AddTo(Src: buffer, Dst: buffer, K: number)
	local TotalElements = K * N
	local Modulus = Q

	for I = 0, TotalElements - 1 do
		local Offset = I * 4
		local SrcValue = buffer.readu32(Src, Offset)
		local DstValue = buffer.readu32(Dst, Offset)

		local Sum = DstValue + SrcValue
		Sum = if Sum >= Modulus then Sum - Modulus else Sum

		buffer.writeu32(Dst, Offset, Sum)
	end
end

function PolyVec.Negate(Vec: buffer, K: number)
	local TotalElements = K * N
	local Modulus = Q

	for I = 0, TotalElements - 1 do
		local Offset = I * 4
		local Value = buffer.readu32(Vec, Offset)
		local Negated = if Value == 0 then 0 else Modulus - Value

		buffer.writeu32(Vec, Offset, Negated)
	end
end

function PolyVec.SubFromX(Vec: buffer, K: number, X: number)
	local Modulus = Q
	local Num = N
	for I = 0, K - 1 do
		local Offset = I * Num * 4

		for J = 0, Num - 1 do
			local JOffset = Offset + J * 4
			local PolyValue = buffer.readu32(Vec, JOffset)

			local Result = if X >= PolyValue then X - PolyValue else X - PolyValue + Modulus

			buffer.writeu32(Vec, JOffset, Result)
		end
	end
end

function PolyVec.Encode(Src: buffer, Dst: buffer, K: number, Sbw: number)
	local PolyByteLength = math.floor((N * Sbw) / 8)
	local Num = N

	for I = 0, K - 1 do
		local SrcOffset = I * Num * 4
		local DstOffset = I * PolyByteLength

		if Sbw == 3 then
			local ItrCnt = 32
			for J = 0, ItrCnt - 1 do
				local POffset = SrcOffset + J * 32
				local BOffset = DstOffset + J * 3

				local P0 = bit32.band(buffer.readu32(Src, POffset + 0), 0x7)
				local P1 = bit32.band(buffer.readu32(Src, POffset + 4), 0x7)
				local P2 = bit32.band(buffer.readu32(Src, POffset + 8), 0x7)
				local P3 = bit32.band(buffer.readu32(Src, POffset + 12), 0x7)
				local P4 = bit32.band(buffer.readu32(Src, POffset + 16), 0x7)
				local P5 = bit32.band(buffer.readu32(Src, POffset + 20), 0x7)
				local P6 = bit32.band(buffer.readu32(Src, POffset + 24), 0x7)
				local P7 = bit32.band(buffer.readu32(Src, POffset + 28), 0x7)

				buffer.writeu8(
					Dst,
					BOffset + 0,
					bit32.bor(bit32.lshift(bit32.band(P2, 0x3), 6), bit32.lshift(P1, 3), P0)
				)
				buffer.writeu8(
					Dst,
					BOffset + 1,
					bit32.bor(
						bit32.lshift(bit32.band(P5, 0x1), 7),
						bit32.lshift(P4, 4),
						bit32.lshift(P3, 1),
						bit32.rshift(P2, 2)
					)
				)
				buffer.writeu8(
					Dst,
					BOffset + 2,
					bit32.bor(bit32.lshift(P7, 5), bit32.lshift(P6, 2), bit32.rshift(P5, 1))
				)
			end
		elseif Sbw == 4 then
			for J = 0, 127 do
				local POffset = SrcOffset + J * 8
				local P0 = bit32.band(buffer.readu32(Src, POffset), 0xF)
				local P1 = bit32.band(buffer.readu32(Src, POffset + 4), 0xF)
				buffer.writeu8(Dst, DstOffset + J, bit32.bor(bit32.lshift(P1, 4), P0))
			end
		elseif Sbw == 6 then
			for J = 0, 63 do
				local POffset = SrcOffset + J * 16
				local BOffset = DstOffset + J * 3

				local P0 = bit32.band(buffer.readu32(Src, POffset + 0), 0x3F)
				local P1 = bit32.band(buffer.readu32(Src, POffset + 4), 0x3F)
				local P2 = bit32.band(buffer.readu32(Src, POffset + 8), 0x3F)
				local P3 = bit32.band(buffer.readu32(Src, POffset + 12), 0x3F)

				buffer.writeu8(Dst, BOffset + 0, bit32.bor(bit32.lshift(bit32.band(P1, 0x3), 6), P0))
				buffer.writeu8(Dst, BOffset + 1, bit32.bor(bit32.lshift(bit32.band(P2, 0xF), 4), bit32.rshift(P1, 2)))
				buffer.writeu8(Dst, BOffset + 2, bit32.bor(bit32.lshift(P3, 2), bit32.rshift(P2, 4)))
			end
		elseif Sbw == 10 then
			for J = 0, 63 do
				local POffset = SrcOffset + J * 16
				local BOffset = DstOffset + J * 5

				local P0 = bit32.band(buffer.readu32(Src, POffset + 0), 0x3FF)
				local P1 = bit32.band(buffer.readu32(Src, POffset + 4), 0x3FF)
				local P2 = bit32.band(buffer.readu32(Src, POffset + 8), 0x3FF)
				local P3 = bit32.band(buffer.readu32(Src, POffset + 12), 0x3FF)

				buffer.writeu8(Dst, BOffset + 0, bit32.band(P0, 0xFF))
				buffer.writeu8(Dst, BOffset + 1, bit32.bor(bit32.lshift(bit32.band(P1, 0x3F), 2), bit32.rshift(P0, 8)))
				buffer.writeu8(Dst, BOffset + 2, bit32.bor(bit32.lshift(bit32.band(P2, 0xF), 4), bit32.rshift(P1, 6)))
				buffer.writeu8(Dst, BOffset + 3, bit32.bor(bit32.lshift(bit32.band(P3, 0x3), 6), bit32.rshift(P2, 4)))
				buffer.writeu8(Dst, BOffset + 4, bit32.rshift(P3, 2))
			end
		elseif Sbw == 13 then
			for J = 0, 31 do
				local POffset = SrcOffset + J * 32
				local BOffset = DstOffset + J * 13

				local P0 = bit32.band(buffer.readu32(Src, POffset + 0), 0x1FFF)
				local P1 = bit32.band(buffer.readu32(Src, POffset + 4), 0x1FFF)
				local P2 = bit32.band(buffer.readu32(Src, POffset + 8), 0x1FFF)
				local P3 = bit32.band(buffer.readu32(Src, POffset + 12), 0x1FFF)
				local P4 = bit32.band(buffer.readu32(Src, POffset + 16), 0x1FFF)
				local P5 = bit32.band(buffer.readu32(Src, POffset + 20), 0x1FFF)
				local P6 = bit32.band(buffer.readu32(Src, POffset + 24), 0x1FFF)
				local P7 = bit32.band(buffer.readu32(Src, POffset + 28), 0x1FFF)

				buffer.writeu8(Dst, BOffset + 0, bit32.band(P0, 0xFF))
				buffer.writeu8(Dst, BOffset + 1, bit32.bor(bit32.lshift(bit32.band(P1, 0x7), 5), bit32.rshift(P0, 8)))
				buffer.writeu8(Dst, BOffset + 2, bit32.rshift(P1, 3))
				buffer.writeu8(Dst, BOffset + 3, bit32.bor(bit32.lshift(bit32.band(P2, 0x3F), 2), bit32.rshift(P1, 11)))
				buffer.writeu8(Dst, BOffset + 4, bit32.bor(bit32.lshift(bit32.band(P3, 0x1), 7), bit32.rshift(P2, 6)))
				buffer.writeu8(Dst, BOffset + 5, bit32.rshift(P3, 1))
				buffer.writeu8(Dst, BOffset + 6, bit32.bor(bit32.lshift(bit32.band(P4, 0xF), 4), bit32.rshift(P3, 9)))
				buffer.writeu8(Dst, BOffset + 7, bit32.rshift(P4, 4))
				buffer.writeu8(Dst, BOffset + 8, bit32.bor(bit32.lshift(bit32.band(P5, 0x7F), 1), bit32.rshift(P4, 12)))
				buffer.writeu8(Dst, BOffset + 9, bit32.bor(bit32.lshift(bit32.band(P6, 0x3), 6), bit32.rshift(P5, 7)))
				buffer.writeu8(Dst, BOffset + 10, bit32.rshift(P6, 2))
				buffer.writeu8(
					Dst,
					BOffset + 11,
					bit32.bor(bit32.lshift(bit32.band(P7, 0x1F), 3), bit32.rshift(P6, 10))
				)
				buffer.writeu8(Dst, BOffset + 12, bit32.rshift(P7, 5))
			end
		elseif Sbw == 18 then
			for J = 0, 63 do
				local POffset = SrcOffset + J * 16
				local BOffset = DstOffset + J * 9

				local P0 = bit32.band(buffer.readu32(Src, POffset + 0), 0x3FFFF)
				local P1 = bit32.band(buffer.readu32(Src, POffset + 4), 0x3FFFF)
				local P2 = bit32.band(buffer.readu32(Src, POffset + 8), 0x3FFFF)
				local P3 = bit32.band(buffer.readu32(Src, POffset + 12), 0x3FFFF)

				buffer.writeu8(Dst, BOffset + 0, bit32.band(P0, 0xFF))
				buffer.writeu8(Dst, BOffset + 1, bit32.band(bit32.rshift(P0, 8), 0xFF))
				buffer.writeu8(Dst, BOffset + 2, bit32.bor(bit32.rshift(P0, 16), bit32.lshift(bit32.band(P1, 0x3F), 2)))
				buffer.writeu8(Dst, BOffset + 3, bit32.band(bit32.rshift(P1, 6), 0xFF))
				buffer.writeu8(Dst, BOffset + 4, bit32.bor(bit32.rshift(P1, 14), bit32.lshift(bit32.band(P2, 0xF), 4)))
				buffer.writeu8(Dst, BOffset + 5, bit32.band(bit32.rshift(P2, 4), 0xFF))
				buffer.writeu8(Dst, BOffset + 6, bit32.bor(bit32.rshift(P2, 12), bit32.lshift(bit32.band(P3, 0x3), 6)))
				buffer.writeu8(Dst, BOffset + 7, bit32.band(bit32.rshift(P3, 2), 0xFF))
				buffer.writeu8(Dst, BOffset + 8, bit32.rshift(P3, 10))
			end
		elseif Sbw == 20 then
			for J = 0, 63 do
				local POffset = SrcOffset + J * 16
				local BOffset = DstOffset + J * 10

				local P0 = bit32.band(buffer.readu32(Src, POffset + 0), 0xFFFFF)
				local P1 = bit32.band(buffer.readu32(Src, POffset + 4), 0xFFFFF)
				local P2 = bit32.band(buffer.readu32(Src, POffset + 8), 0xFFFFF)
				local P3 = bit32.band(buffer.readu32(Src, POffset + 12), 0xFFFFF)

				buffer.writeu8(Dst, BOffset + 0, bit32.band(P0, 0xFF))
				buffer.writeu8(Dst, BOffset + 1, bit32.band(bit32.rshift(P0, 8), 0xFF))
				buffer.writeu8(Dst, BOffset + 2, bit32.bor(bit32.rshift(P0, 16), bit32.lshift(bit32.band(P1, 0xF), 4)))
				buffer.writeu8(Dst, BOffset + 3, bit32.band(bit32.rshift(P1, 4), 0xFF))
				buffer.writeu8(Dst, BOffset + 4, bit32.band(bit32.rshift(P1, 12), 0xFF))
				buffer.writeu8(Dst, BOffset + 5, bit32.band(P2, 0xFF))
				buffer.writeu8(Dst, BOffset + 6, bit32.band(bit32.rshift(P2, 8), 0xFF))
				buffer.writeu8(Dst, BOffset + 7, bit32.bor(bit32.rshift(P2, 16), bit32.lshift(bit32.band(P3, 0xF), 4)))
				buffer.writeu8(Dst, BOffset + 8, bit32.band(bit32.rshift(P3, 4), 0xFF))
				buffer.writeu8(Dst, BOffset + 9, bit32.rshift(P3, 12))
			end
		else
			local PolyBuffer = buffer.create(N * 4)
			local ByteBuffer = buffer.create(PolyByteLength)
			buffer.copy(PolyBuffer, 0, Src, SrcOffset, N * 4)
			BitPacking.Encode(PolyBuffer, ByteBuffer, Sbw)
			buffer.copy(Dst, DstOffset, ByteBuffer, 0, PolyByteLength)
		end
	end
end

function PolyVec.Decode(Src: buffer, Dst: buffer, K: number, Sbw: number)
	local PolyByteLength = math.floor((N * Sbw) / 8)
	local Num = N

	for I = 0, K - 1 do
		local SrcOffset = I * PolyByteLength
		local DstOffset = I * Num * 4

		if Sbw == 3 then
			for J = 0, 31 do
				local BOffset = SrcOffset + J * 3
				local POffset = DstOffset + J * 32

				local B0 = buffer.readu8(Src, BOffset + 0)
				local B1 = buffer.readu8(Src, BOffset + 1)
				local B2 = buffer.readu8(Src, BOffset + 2)

				buffer.writeu32(Dst, POffset + 0, bit32.band(B0, 0x7))
				buffer.writeu32(Dst, POffset + 4, bit32.band(bit32.rshift(B0, 3), 0x7))
				buffer.writeu32(Dst, POffset + 8, bit32.bor(bit32.rshift(B0, 6), bit32.lshift(bit32.band(B1, 0x1), 2)))
				buffer.writeu32(Dst, POffset + 12, bit32.band(bit32.rshift(B1, 1), 0x7))
				buffer.writeu32(Dst, POffset + 16, bit32.band(bit32.rshift(B1, 4), 0x7))
				buffer.writeu32(Dst, POffset + 20, bit32.bor(bit32.rshift(B1, 7), bit32.lshift(bit32.band(B2, 0x3), 1)))
				buffer.writeu32(Dst, POffset + 24, bit32.band(bit32.rshift(B2, 2), 0x7))
				buffer.writeu32(Dst, POffset + 28, bit32.rshift(B2, 5))
			end
		elseif Sbw == 4 then
			for J = 0, 127 do
				local B = buffer.readu8(Src, SrcOffset + J)
				local POffset = DstOffset + J * 8
				buffer.writeu32(Dst, POffset, bit32.band(B, 0xF))
				buffer.writeu32(Dst, POffset + 4, bit32.rshift(B, 4))
			end
		elseif Sbw == 6 then
			for J = 0, 63 do
				local BOffset = SrcOffset + J * 3
				local POffset = DstOffset + J * 16

				local B0 = buffer.readu8(Src, BOffset + 0)
				local B1 = buffer.readu8(Src, BOffset + 1)
				local B2 = buffer.readu8(Src, BOffset + 2)

				buffer.writeu32(Dst, POffset + 0, bit32.band(B0, 0x3F))
				buffer.writeu32(Dst, POffset + 4, bit32.bor(bit32.rshift(B0, 6), bit32.lshift(bit32.band(B1, 0xF), 2)))
				buffer.writeu32(Dst, POffset + 8, bit32.bor(bit32.rshift(B1, 4), bit32.lshift(bit32.band(B2, 0x3), 4)))
				buffer.writeu32(Dst, POffset + 12, bit32.rshift(B2, 2))
			end
		elseif Sbw == 10 then
			for J = 0, 63 do
				local BOffset = SrcOffset + J * 5
				local POffset = DstOffset + J * 16

				local B0 = buffer.readu8(Src, BOffset + 0)
				local B1 = buffer.readu8(Src, BOffset + 1)
				local B2 = buffer.readu8(Src, BOffset + 2)
				local B3 = buffer.readu8(Src, BOffset + 3)
				local B4 = buffer.readu8(Src, BOffset + 4)

				buffer.writeu32(Dst, POffset + 0, bit32.bor(B0, bit32.lshift(bit32.band(B1, 0x3), 8)))
				buffer.writeu32(Dst, POffset + 4, bit32.bor(bit32.rshift(B1, 2), bit32.lshift(bit32.band(B2, 0xF), 6)))
				buffer.writeu32(Dst, POffset + 8, bit32.bor(bit32.rshift(B2, 4), bit32.lshift(bit32.band(B3, 0x3F), 4)))
				buffer.writeu32(Dst, POffset + 12, bit32.bor(bit32.rshift(B3, 6), bit32.lshift(B4, 2)))
			end
		elseif Sbw == 13 then
			for J = 0, 31 do
				local BOffset = SrcOffset + J * 13
				local POffset = DstOffset + J * 32

				local B0 = buffer.readu8(Src, BOffset + 0)
				local B1 = buffer.readu8(Src, BOffset + 1)
				local B2 = buffer.readu8(Src, BOffset + 2)
				local B3 = buffer.readu8(Src, BOffset + 3)
				local B4 = buffer.readu8(Src, BOffset + 4)
				local B5 = buffer.readu8(Src, BOffset + 5)
				local B6 = buffer.readu8(Src, BOffset + 6)
				local B7 = buffer.readu8(Src, BOffset + 7)
				local B8 = buffer.readu8(Src, BOffset + 8)
				local B9 = buffer.readu8(Src, BOffset + 9)
				local B10 = buffer.readu8(Src, BOffset + 10)
				local B11 = buffer.readu8(Src, BOffset + 11)
				local B12 = buffer.readu8(Src, BOffset + 12)

				buffer.writeu32(Dst, POffset + 0, bit32.bor(B0, bit32.lshift(bit32.band(B1, 0x1F), 8)))
				buffer.writeu32(
					Dst,
					POffset + 4,
					bit32.bor(bit32.rshift(B1, 5), bit32.lshift(B2, 3), bit32.lshift(bit32.band(B3, 0x3), 11))
				)
				buffer.writeu32(Dst, POffset + 8, bit32.bor(bit32.rshift(B3, 2), bit32.lshift(bit32.band(B4, 0x7F), 6)))
				buffer.writeu32(
					Dst,
					POffset + 12,
					bit32.bor(bit32.rshift(B4, 7), bit32.lshift(B5, 1), bit32.lshift(bit32.band(B6, 0xF), 9))
				)
				buffer.writeu32(
					Dst,
					POffset + 16,
					bit32.bor(bit32.rshift(B6, 4), bit32.lshift(B7, 4), bit32.lshift(bit32.band(B8, 0x1), 12))
				)
				buffer.writeu32(
					Dst,
					POffset + 20,
					bit32.bor(bit32.rshift(B8, 1), bit32.lshift(bit32.band(B9, 0x3F), 7))
				)
				buffer.writeu32(
					Dst,
					POffset + 24,
					bit32.bor(bit32.rshift(B9, 6), bit32.lshift(B10, 2), bit32.lshift(bit32.band(B11, 0x7), 10))
				)
				buffer.writeu32(Dst, POffset + 28, bit32.bor(bit32.rshift(B11, 3), bit32.lshift(B12, 5)))
			end
		elseif Sbw == 18 then
			for J = 0, 63 do
				local BOffset = SrcOffset + J * 9
				local POffset = DstOffset + J * 16

				local B0 = buffer.readu8(Src, BOffset + 0)
				local B1 = buffer.readu8(Src, BOffset + 1)
				local B2 = buffer.readu8(Src, BOffset + 2)
				local B3 = buffer.readu8(Src, BOffset + 3)
				local B4 = buffer.readu8(Src, BOffset + 4)
				local B5 = buffer.readu8(Src, BOffset + 5)
				local B6 = buffer.readu8(Src, BOffset + 6)
				local B7 = buffer.readu8(Src, BOffset + 7)
				local B8 = buffer.readu8(Src, BOffset + 8)

				buffer.writeu32(
					Dst,
					POffset + 0,
					bit32.bor(B0, bit32.lshift(B1, 8), bit32.lshift(bit32.band(B2, 0x3), 16))
				)
				buffer.writeu32(
					Dst,
					POffset + 4,
					bit32.bor(bit32.rshift(B2, 2), bit32.lshift(B3, 6), bit32.lshift(bit32.band(B4, 0xF), 14))
				)
				buffer.writeu32(
					Dst,
					POffset + 8,
					bit32.bor(bit32.rshift(B4, 4), bit32.lshift(B5, 4), bit32.lshift(bit32.band(B6, 0x3F), 12))
				)
				buffer.writeu32(
					Dst,
					POffset + 12,
					bit32.bor(bit32.rshift(B6, 6), bit32.lshift(B7, 2), bit32.lshift(B8, 10))
				)
			end
		elseif Sbw == 20 then
			for J = 0, 63 do
				local BOffset = SrcOffset + J * 10
				local POffset = DstOffset + J * 16

				local B0 = buffer.readu8(Src, BOffset + 0)
				local B1 = buffer.readu8(Src, BOffset + 1)
				local B2 = buffer.readu8(Src, BOffset + 2)
				local B3 = buffer.readu8(Src, BOffset + 3)
				local B4 = buffer.readu8(Src, BOffset + 4)
				local B5 = buffer.readu8(Src, BOffset + 5)
				local B6 = buffer.readu8(Src, BOffset + 6)
				local B7 = buffer.readu8(Src, BOffset + 7)
				local B8 = buffer.readu8(Src, BOffset + 8)
				local B9 = buffer.readu8(Src, BOffset + 9)

				buffer.writeu32(
					Dst,
					POffset + 0,
					bit32.bor(B0, bit32.lshift(B1, 8), bit32.lshift(bit32.band(B2, 0xF), 16))
				)
				buffer.writeu32(
					Dst,
					POffset + 4,
					bit32.bor(bit32.rshift(B2, 4), bit32.lshift(B3, 4), bit32.lshift(B4, 12))
				)
				buffer.writeu32(
					Dst,
					POffset + 8,
					bit32.bor(B5, bit32.lshift(B6, 8), bit32.lshift(bit32.band(B7, 0xF), 16))
				)
				buffer.writeu32(
					Dst,
					POffset + 12,
					bit32.bor(bit32.rshift(B7, 4), bit32.lshift(B8, 4), bit32.lshift(B9, 12))
				)
			end
		else
			local ByteBuffer = buffer.create(PolyByteLength)
			local PolyBuffer = buffer.create(N * 4)
			buffer.copy(ByteBuffer, 0, Src, SrcOffset, PolyByteLength)
			BitPacking.Decode(ByteBuffer, PolyBuffer, Sbw)
			buffer.copy(Dst, DstOffset, PolyBuffer, 0, N * 4)
		end
	end
end

function PolyVec.HighBits(Src: buffer, Dst: buffer, K: number, Alpha: number)
	local T0 = bit32.rshift(Alpha, 1)
	local T1 = Q - 1
	local Modulus = Q
	local Num = N

	for I = 0, K - 1 do
		local Offset = I * Num * 4

		for J = 0, Num - 1 do
			local JOffset = Offset + J * 4
			local R = buffer.readu32(Src, JOffset)

			local T2 = R + T0 - 1
			local T3 = math.floor(T2 / Alpha)
			local T4 = T3 * Alpha

			local R0 = if R >= T4 then R - T4 else R - T4 + Modulus
			local T5 = if R >= R0 then R - R0 else R - R0 + Modulus

			local Flag = (T5 == T1)
			local R1 = if Flag then 0 else T3

			buffer.writeu32(Dst, JOffset, R1)
		end
	end
end

function PolyVec.LowBits(Src: buffer, Dst: buffer, K: number, Alpha: number)
	local T0 = bit32.rshift(Alpha, 1)
	local T1 = Q - 1

	local Modulus = Q
	local Num = N

	for I = 0, K - 1 do
		local Offset = I * Num * 4

		for J = 0, Num - 1 do
			local JOffset = Offset + J * 4
			local R = buffer.readu32(Src, JOffset)

			local T2 = R + T0 - 1
			local T3 = math.floor(T2 / Alpha)
			local T4 = T3 * Alpha

			local R0 = if R >= T4 then R - T4 else R - T4 + Modulus
			local T5 = if R >= R0 then R - R0 else R - R0 + Modulus

			local Flag = (T5 == T1)
			local R0_ = if Flag then (if R0 >= 1 then R0 - 1 else R0 - 1 + Modulus) else R0

			buffer.writeu32(Dst, JOffset, R0_)
		end
	end
end

function PolyVec.MultiplyByPoly(PolyBuffer: buffer, SrcVec: buffer, DstVec: buffer, K: number)
	local Modulus = Q
	local Num = N

	for I = 0, K - 1 do
		local Offset = I * Num * 4

		for J = 0, Num - 1 do
			local JOffset = Offset + J * 4
			local PolyValue = buffer.readu32(PolyBuffer, J * 4)
			local SrcValue = buffer.readu32(SrcVec, JOffset)

			local Product = (PolyValue * SrcValue) % Modulus

			buffer.writeu32(DstVec, JOffset, Product)
		end
	end
end

function PolyVec.InfinityNorm(Vec: buffer, K: number): number
	local Result = 0
	local QBy2 = math.floor(Q / 2)
	local Modulus = Q
	local Num = N

	for I = 0, K - 1 do
		local Offset = I * Num * 4

		for J = 0, Num - 1 do
			local JOffset = Offset + J * 4
			local PolyValue = buffer.readu32(Vec, JOffset)

			local CurrentValue = if PolyValue > QBy2
				then (if PolyValue == 0 then 0 else Modulus - PolyValue)
				else PolyValue
			if CurrentValue > Result then
				Result = CurrentValue
			end
		end
	end

	return Result
end

function PolyVec.MakeHint(PolyA: buffer, PolyB: buffer, PolyC: buffer, K: number, Alpha: number)
	local T0 = bit32.rshift(Alpha, 1)
	local T1 = Q - 1

	local Num = N
	local Modulus = Q

	for I = 0, K - 1 do
		local Offset = I * Num * 4

		for J = 0, Num - 1 do
			local JOffset = Offset + J * 4
			local Z = buffer.readu32(PolyA, JOffset)
			local R = buffer.readu32(PolyB, JOffset)

			local T2_R = R + T0 - 1
			local T3_R = math.floor(T2_R / Alpha)
			local T4_R = T3_R * Alpha
			local R0_R = if R >= T4_R then R - T4_R else R - T4_R + Modulus
			local T5_R = if R >= R0_R then R - R0_R else R - R0_R + Modulus
			local Flag_R = (T5_R == T1)
			local R1 = if Flag_R then 0 else T3_R

			local Sum = R + Z
			Sum = if Sum >= Modulus then Sum - Modulus else Sum

			local T2_V = Sum + T0 - 1
			local T3_V = math.floor(T2_V / Alpha)
			local T4_V = T3_V * Alpha
			local R0_V = if Sum >= T4_V then Sum - T4_V else Sum - T4_V + Modulus
			local T5_V = if Sum >= R0_V then Sum - R0_V else Sum - R0_V + Modulus
			local Flag_V = (T5_V == T1)
			local V1 = if Flag_V then 0 else T3_V

			local Hint = if R1 ~= V1 then 1 else 0
			buffer.writeu32(PolyC, JOffset, Hint)
		end
	end
end

function PolyVec.UseHint(PolyH: buffer, PolyR: buffer, PolyRZ: buffer, K: number, Alpha: number)
	local M = math.floor((Q - 1) / Alpha)
	local T0 = bit32.rshift(Alpha, 1)
	local T1 = Q - T0
	local T1_Q = Q - 1
	local Modulus = Q

	for I = 0, K - 1 do
		local Offset = I * N * 4

		for J = 0, N - 1 do
			local JOffset = Offset + J * 4
			local H = buffer.readu32(PolyH, JOffset)
			local R = buffer.readu32(PolyR, JOffset)

			local T2 = R + T0 - 1
			local T3 = math.floor(T2 / Alpha)
			local T4 = T3 * Alpha

			local R0 = if R >= T4 then R - T4 else R - T4 + Modulus

			local T5 = if R >= R0 then R - R0 else R - R0 + Modulus
			local Flag = (T5 == T1_Q)

			local R1 = if Flag then 0 else T3
			local R0_ = if Flag then (if R0 >= 1 then R0 - 1 else R0 - 1 + Modulus) else R0

			if H == 1 then
				if R0_ > 0 and R0_ < T1 then
					R1 += 1
				else
					R1 -= 1
				end
			end

			buffer.writeu32(PolyRZ, JOffset, (R1 % M + M) % M)
		end
	end
end

function PolyVec.Count1s(Vec: buffer, K: number): number
	local Count = 0

	for I = 0, K * N - 1 do
		local Value = buffer.readu32(Vec, I * 4)
		Count += Value
	end

	return Count
end

function PolyVec.LeftShift(Vec: buffer, K: number, D: number)
	local Num = N
	local Modulus = Q

	for I = 0, K - 1 do
		local Offset = I * Num * 4

		for J = 0, Num - 1 do
			local JOffset = Offset + J * 4
			local PolyValue = buffer.readu32(Vec, JOffset)

			local Shifted = bit32.lshift(PolyValue, D)
			local Result = if Shifted >= Modulus then Shifted % Modulus else Shifted

			buffer.writeu32(Vec, JOffset, Result)
		end
	end
end

function PolyVec.Copy(Src: buffer, Dst: buffer, K: number)
	buffer.copy(Dst, 0, Src, 0, K * N * 4)
end

return PolyVec


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

local SHA3 = {}

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: number, Multiplier: number = 0, nil

		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
	local XOR = bit32.bxor

	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,
				XOR(buffer.readu32(LanesLow, Index), buffer.readu32(InputBuffer, BufferPos))
			)

			buffer.writeu32(
				LanesHigh,
				Index,
				XOR(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 =
				XOR(Lane01Low, Lane06Low, Lane11Low, Lane16Low, Lane21Low),
				XOR(Lane01High, Lane06High, Lane11High, Lane16High, Lane21High)
			local Column2Low, Column2High =
				XOR(Lane02Low, Lane07Low, Lane12Low, Lane17Low, Lane22Low),
				XOR(Lane02High, Lane07High, Lane12High, Lane17High, Lane22High)
			local Column3Low, Column3High =
				XOR(Lane03Low, Lane08Low, Lane13Low, Lane18Low, Lane23Low),
				XOR(Lane03High, Lane08High, Lane13High, Lane18High, Lane23High)
			local Column4Low, Column4High =
				XOR(Lane04Low, Lane09Low, Lane14Low, Lane19Low, Lane24Low),
				XOR(Lane04High, Lane09High, Lane14High, Lane19High, Lane24High)
			local Column5Low, Column5High =
				XOR(Lane05Low, Lane10Low, Lane15Low, Lane20Low, Lane25Low),
				XOR(Lane05High, Lane10High, Lane15High, Lane20High, Lane25High)

			local DeltaLow, DeltaHigh =
				XOR(Column1Low, Column3Low * 2 + Column3High // 2147483648),
				XOR(Column1High, Column3High * 2 + Column3Low // 2147483648)
			local Temp0Low, Temp0High = XOR(DeltaLow, Lane02Low), XOR(DeltaHigh, Lane02High)
			local Temp1Low, Temp1High = XOR(DeltaLow, Lane07Low), XOR(DeltaHigh, Lane07High)
			local Temp2Low, Temp2High = XOR(DeltaLow, Lane12Low), XOR(DeltaHigh, Lane12High)
			local Temp3Low, Temp3High = XOR(DeltaLow, Lane17Low), XOR(DeltaHigh, Lane17High)
			local Temp4Low, Temp4High = XOR(DeltaLow, Lane22Low), XOR(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 = XOR(Column2Low, Column4Low * 2 + Column4High // 2147483648)
			DeltaHigh = XOR(Column2High, Column4High * 2 + Column4Low // 2147483648)
			Temp0Low = XOR(DeltaLow, Lane03Low)
			Temp0High = XOR(DeltaHigh, Lane03High)
			Temp1Low = XOR(DeltaLow, Lane08Low)
			Temp1High = XOR(DeltaHigh, Lane08High)
			Temp2Low = XOR(DeltaLow, Lane13Low)
			Temp2High = XOR(DeltaHigh, Lane13High)
			Temp3Low = XOR(DeltaLow, Lane18Low)
			Temp3High = XOR(DeltaHigh, Lane18High)
			Temp4Low = XOR(DeltaLow, Lane23Low)
			Temp4High = XOR(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 = XOR(Column3Low, Column5Low * 2 + Column5High // 2147483648)
			DeltaHigh = XOR(Column3High, Column5High * 2 + Column5Low // 2147483648)
			Temp0Low = XOR(DeltaLow, Lane04Low)
			Temp0High = XOR(DeltaHigh, Lane04High)
			Temp1Low = XOR(DeltaLow, Lane09Low)
			Temp1High = XOR(DeltaHigh, Lane09High)
			Temp2Low = XOR(DeltaLow, Lane14Low)
			Temp2High = XOR(DeltaHigh, Lane14High)
			Temp3Low = XOR(DeltaLow, Lane19Low)
			Temp3High = XOR(DeltaHigh, Lane19High)
			Temp4Low = XOR(DeltaLow, Lane24Low)
			Temp4High = XOR(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 = XOR(Column4Low, Column1Low * 2 + Column1High // 2147483648)
			DeltaHigh = XOR(Column4High, Column1High * 2 + Column1Low // 2147483648)

			Temp0Low = XOR(DeltaLow, Lane05Low)
			Temp0High = XOR(DeltaHigh, Lane05High)
			Temp1Low = XOR(DeltaLow, Lane10Low)
			Temp1High = XOR(DeltaHigh, Lane10High)
			Temp2Low = XOR(DeltaLow, Lane15Low)
			Temp2High = XOR(DeltaHigh, Lane15High)
			Temp3Low = XOR(DeltaLow, Lane20Low)
			Temp3High = XOR(DeltaHigh, Lane20High)
			Temp4Low = XOR(DeltaLow, Lane25Low)
			Temp4High = XOR(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 = XOR(Column5Low, Column2Low * 2 + Column2High // 2147483648)
			DeltaHigh = XOR(Column5High, Column2High * 2 + Column2Low // 2147483648)
			Temp1Low = XOR(DeltaLow, Lane06Low)
			Temp1High = XOR(DeltaHigh, Lane06High)
			Temp2Low = XOR(DeltaLow, Lane11Low)
			Temp2High = XOR(DeltaHigh, Lane11High)
			Temp3Low = XOR(DeltaLow, Lane16Low)
			Temp3High = XOR(DeltaHigh, Lane16High)
			Temp4Low = XOR(DeltaLow, Lane21Low)
			Temp4High = XOR(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 = XOR(DeltaLow, Lane01Low)
			Lane01High = XOR(DeltaHigh, Lane01High)

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

			Lane01Low = XOR(Lane01Low, buffer.readu32(RCLow, RoundIndex))
			Lane01High = XOR(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
): 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 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

	return Output
end

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

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

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

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

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

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

return SHA3


--[=[
	ML-DSA Sampling

	Example usage:
		local Sampling = require(script)

		local Rho = buffer.create(32)
		local Matrix = buffer.create(4 * 4 * 256 * 4)
		Sampling.ExpandA(Rho, Matrix, 4, 4)
--]=]

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

local XOF = require("./XOF")
local BitPacking = require("./Pack")
local Params = require("./Params")

local N = 256
local Q = 8380417

local Sampling = {}

local WORKSPACE_MSG_34 = buffer.create(34)
local WORKSPACE_MSG_66 = buffer.create(66)

function Sampling.ExpandA(Rho: buffer, Mat: buffer, K: number, L: number)
	buffer.copy(WORKSPACE_MSG_34, 0, Rho, 0, 32)

	local Message = WORKSPACE_MSG_34
	local Num = N
	local Modulus = Q
	local XOF = XOF

	for I = 0, K - 1 do
		for J = 0, L - 1 do
			local Offset = (I * L + J) * Num * 4

			buffer.writeu8(Message, 32, J)
			buffer.writeu8(Message, 33, I)

			XOF.Reset128()
			XOF.Absorb128(Message)

			local CoeffCount = 0
			local ChunkSize = 504

			while CoeffCount < Num do
				local TotalOutput = XOF.Squeeze128(ChunkSize)
				local ByteOffset = 0
				local OutputLen = buffer.len(TotalOutput)

				while CoeffCount < Num and ByteOffset + 5 < OutputLen do
					local Chunk = buffer.readu32(TotalOutput, ByteOffset)
					local B3 = buffer.readu8(TotalOutput, ByteOffset + 4)
					local B4 = buffer.readu8(TotalOutput, ByteOffset + 5)

					local T3 = bit32.band(Chunk, 0x7FFFFF)
					if T3 < Modulus then
						buffer.writeu32(Mat, Offset + CoeffCount * 4, T3)
						CoeffCount += 1
					end

					local T4 =
						bit32.bor(bit32.rshift(Chunk, 24), bit32.lshift(B3, 8), bit32.lshift(bit32.band(B4, 0x7F), 16))
					if T4 < Modulus and CoeffCount < Num then
						buffer.writeu32(Mat, Offset + CoeffCount * 4, T4)
						CoeffCount += 1
					end

					ByteOffset += 6
				end

				while CoeffCount < Num and ByteOffset + 2 < OutputLen do
					local T0 = bit32.band(buffer.readu8(TotalOutput, ByteOffset + 2), 0x7F)
					local T1 = buffer.readu8(TotalOutput, ByteOffset + 1)
					local T2 = buffer.readu8(TotalOutput, ByteOffset)

					local T3 = bit32.bor(bit32.lshift(T0, 16), bit32.lshift(T1, 8), T2)

					if T3 < Modulus then
						buffer.writeu32(Mat, Offset + CoeffCount * 4, T3)
						CoeffCount += 1
					end

					ByteOffset += 3
				end
			end
		end
	end
end

function Sampling.ExpandS(RhoPrime: buffer, Vec: buffer, Eta: number, K: number, StartNonce: number)
	if not Params.CheckEta(Eta) or not Params.CheckNonce(StartNonce) then
		error("Invalid parameters for ExpandS")
	end

	buffer.copy(WORKSPACE_MSG_66, 0, RhoPrime, 0, 64)

	local Message = WORKSPACE_MSG_66
	local Num = N
	local Modulus = Q

	for I = 0, K - 1 do
		local Offset = I * Num * 4
		local NewNonce = StartNonce + I

		buffer.writeu8(Message, 64, bit32.band(NewNonce, 0xFF))
		buffer.writeu8(Message, 65, bit32.band(bit32.rshift(NewNonce, 8), 0xFF))

		XOF.Reset256()
		XOF.Absorb256(Message)

		local CoeffCount = 0
		local ChunkSize = if Eta == 2 then 136 else 272

		while CoeffCount < Num do
			local TotalOutput = XOF.Squeeze256(ChunkSize)
			local ByteOffset = 0
			local OutputLen = buffer.len(TotalOutput)

			if Eta == 2 then
				while CoeffCount < Num and ByteOffset + 3 < OutputLen do
					local Chunk = buffer.readu32(TotalOutput, ByteOffset)

					local T0 = bit32.band(Chunk, 0x0F)
					local T1 = bit32.band(bit32.rshift(Chunk, 4), 0x0F)
					local T2 = bit32.band(bit32.rshift(Chunk, 8), 0x0F)
					local T3 = bit32.band(bit32.rshift(Chunk, 12), 0x0F)
					local T4 = bit32.band(bit32.rshift(Chunk, 16), 0x0F)
					local T5 = bit32.band(bit32.rshift(Chunk, 20), 0x0F)
					local T6 = bit32.band(bit32.rshift(Chunk, 24), 0x0F)
					local T7 = bit32.band(bit32.rshift(Chunk, 28), 0x0F)

					if T0 < 15 and CoeffCount < Num then
						buffer.writeu32(
							Vec,
							Offset + CoeffCount * 4,
							2 - (T0 % 5) + (if (T0 % 5) > 2 then Modulus else 0)
						)
						CoeffCount += 1
					end
					if T1 < 15 and CoeffCount < Num then
						buffer.writeu32(
							Vec,
							Offset + CoeffCount * 4,
							2 - (T1 % 5) + (if (T1 % 5) > 2 then Modulus else 0)
						)
						CoeffCount += 1
					end
					if T2 < 15 and CoeffCount < Num then
						buffer.writeu32(
							Vec,
							Offset + CoeffCount * 4,
							2 - (T2 % 5) + (if (T2 % 5) > 2 then Modulus else 0)
						)
						CoeffCount += 1
					end
					if T3 < 15 and CoeffCount < Num then
						buffer.writeu32(
							Vec,
							Offset + CoeffCount * 4,
							2 - (T3 % 5) + (if (T3 % 5) > 2 then Modulus else 0)
						)
						CoeffCount += 1
					end
					if T4 < 15 and CoeffCount < Num then
						buffer.writeu32(
							Vec,
							Offset + CoeffCount * 4,
							2 - (T4 % 5) + (if (T4 % 5) > 2 then Modulus else 0)
						)
						CoeffCount += 1
					end
					if T5 < 15 and CoeffCount < Num then
						buffer.writeu32(
							Vec,
							Offset + CoeffCount * 4,
							2 - (T5 % 5) + (if (T5 % 5) > 2 then Modulus else 0)
						)
						CoeffCount += 1
					end
					if T6 < 15 and CoeffCount < Num then
						buffer.writeu32(
							Vec,
							Offset + CoeffCount * 4,
							2 - (T6 % 5) + (if (T6 % 5) > 2 then Modulus else 0)
						)
						CoeffCount += 1
					end
					if T7 < 15 and CoeffCount < Num then
						buffer.writeu32(
							Vec,
							Offset + CoeffCount * 4,
							2 - (T7 % 5) + (if (T7 % 5) > 2 then Modulus else 0)
						)
						CoeffCount += 1
					end

					ByteOffset += 4
				end
			else
				while CoeffCount < Num and ByteOffset + 3 < OutputLen do
					local Chunk = buffer.readu32(TotalOutput, ByteOffset)

					local T0 = bit32.band(Chunk, 0x0F)
					local T1 = bit32.band(bit32.rshift(Chunk, 4), 0x0F)
					local T2 = bit32.band(bit32.rshift(Chunk, 8), 0x0F)
					local T3 = bit32.band(bit32.rshift(Chunk, 12), 0x0F)
					local T4 = bit32.band(bit32.rshift(Chunk, 16), 0x0F)
					local T5 = bit32.band(bit32.rshift(Chunk, 20), 0x0F)
					local T6 = bit32.band(bit32.rshift(Chunk, 24), 0x0F)
					local T7 = bit32.band(bit32.rshift(Chunk, 28), 0x0F)

					if T0 < 9 and CoeffCount < Num then
						buffer.writeu32(Vec, Offset + CoeffCount * 4, 4 - T0 + (if T0 > 4 then Modulus else 0))
						CoeffCount += 1
					end
					if T1 < 9 and CoeffCount < Num then
						buffer.writeu32(Vec, Offset + CoeffCount * 4, 4 - T1 + (if T1 > 4 then Modulus else 0))
						CoeffCount += 1
					end
					if T2 < 9 and CoeffCount < Num then
						buffer.writeu32(Vec, Offset + CoeffCount * 4, 4 - T2 + (if T2 > 4 then Modulus else 0))
						CoeffCount += 1
					end
					if T3 < 9 and CoeffCount < Num then
						buffer.writeu32(Vec, Offset + CoeffCount * 4, 4 - T3 + (if T3 > 4 then Modulus else 0))
						CoeffCount += 1
					end
					if T4 < 9 and CoeffCount < Num then
						buffer.writeu32(Vec, Offset + CoeffCount * 4, 4 - T4 + (if T4 > 4 then Modulus else 0))
						CoeffCount += 1
					end
					if T5 < 9 and CoeffCount < Num then
						buffer.writeu32(Vec, Offset + CoeffCount * 4, 4 - T5 + (if T5 > 4 then Modulus else 0))
						CoeffCount += 1
					end
					if T6 < 9 and CoeffCount < Num then
						buffer.writeu32(Vec, Offset + CoeffCount * 4, 4 - T6 + (if T6 > 4 then Modulus else 0))
						CoeffCount += 1
					end
					if T7 < 9 and CoeffCount < Num then
						buffer.writeu32(Vec, Offset + CoeffCount * 4, 4 - T7 + (if T7 > 4 then Modulus else 0))
						CoeffCount += 1
					end

					ByteOffset += 4
				end
			end

			while CoeffCount < Num and ByteOffset < OutputLen do
				local Byte = buffer.readu8(TotalOutput, ByteOffset)
				local T0 = bit32.band(Byte, 0x0F)
				local T1 = bit32.band(bit32.rshift(Byte, 4), 0x0F)

				if Eta == 2 then
					if T0 < 15 then
						buffer.writeu32(
							Vec,
							Offset + CoeffCount * 4,
							2 - (T0 % 5) + (if (T0 % 5) > 2 then Modulus else 0)
						)
						CoeffCount += 1
					end
					if CoeffCount < Num and T1 < 15 then
						buffer.writeu32(
							Vec,
							Offset + CoeffCount * 4,
							2 - (T1 % 5) + (if (T1 % 5) > 2 then Modulus else 0)
						)
						CoeffCount += 1
					end
				else
					if T0 < 9 then
						buffer.writeu32(Vec, Offset + CoeffCount * 4, 4 - T0 + (if T0 > 4 then Modulus else 0))
						CoeffCount += 1
					end
					if CoeffCount < Num and T1 < 9 then
						buffer.writeu32(Vec, Offset + CoeffCount * 4, 4 - T1 + (if T1 > 4 then Modulus else 0))
						CoeffCount += 1
					end
				end

				ByteOffset += 1
			end
		end
	end
end

function Sampling.ExpandMask(Seed: buffer, Nonce: number, Vec: buffer, Gamma1: number, L: number)
	if not Params.CheckGamma1(Gamma1) then
		error("Invalid Gamma1 parameter")
	end

	local Gamma1BitWidth = if Gamma1 == 131072 then 18 else 20
	local BufSize = math.floor((N * Gamma1BitWidth) / 8)

	buffer.copy(WORKSPACE_MSG_66, 0, Seed, 0, 64)

	local Message = WORKSPACE_MSG_66
	local Num = N
	local Modulus = Q
	local G1 = Gamma1

	for I = 0, L - 1 do
		local Offset = I * Num * 4
		local NewNonce = Nonce + I

		buffer.writeu8(Message, 64, bit32.band(NewNonce, 0xFF))
		buffer.writeu8(Message, 65, bit32.band(bit32.rshift(NewNonce, 8), 0xFF))

		XOF.Reset256()
		XOF.Absorb256(Message)

		local Src = XOF.Squeeze256(BufSize)

		if Gamma1BitWidth == 18 then
			for J = 0, 63 do
				local BOffset = J * 9
				local POffset = Offset + J * 16

				local B0 = buffer.readu8(Src, BOffset + 0)
				local B1 = buffer.readu8(Src, BOffset + 1)
				local B2 = buffer.readu8(Src, BOffset + 2)
				local B3 = buffer.readu8(Src, BOffset + 3)
				local B4 = buffer.readu8(Src, BOffset + 4)
				local B5 = buffer.readu8(Src, BOffset + 5)
				local B6 = buffer.readu8(Src, BOffset + 6)
				local B7 = buffer.readu8(Src, BOffset + 7)
				local B8 = buffer.readu8(Src, BOffset + 8)

				local V0 = bit32.bor(B0, bit32.lshift(B1, 8), bit32.lshift(bit32.band(B2, 0x3), 16))
				local V1 = bit32.bor(bit32.rshift(B2, 2), bit32.lshift(B3, 6), bit32.lshift(bit32.band(B4, 0xF), 14))
				local V2 = bit32.bor(bit32.rshift(B4, 4), bit32.lshift(B5, 4), bit32.lshift(bit32.band(B6, 0x3F), 12))
				local V3 = bit32.bor(bit32.rshift(B6, 6), bit32.lshift(B7, 2), bit32.lshift(B8, 10))

				local D0 = G1 - V0
				local D1 = G1 - V1
				local D2 = G1 - V2
				local D3 = G1 - V3

				buffer.writeu32(Vec, POffset + 0, if D0 < 0 then D0 + Modulus else D0)
				buffer.writeu32(Vec, POffset + 4, if D1 < 0 then D1 + Modulus else D1)
				buffer.writeu32(Vec, POffset + 8, if D2 < 0 then D2 + Modulus else D2)
				buffer.writeu32(Vec, POffset + 12, if D3 < 0 then D3 + Modulus else D3)
			end
		else
			for J = 0, 63 do
				local BOffset = J * 10
				local POffset = Offset + J * 16

				local B0 = buffer.readu8(Src, BOffset + 0)
				local B1 = buffer.readu8(Src, BOffset + 1)
				local B2 = buffer.readu8(Src, BOffset + 2)
				local B3 = buffer.readu8(Src, BOffset + 3)
				local B4 = buffer.readu8(Src, BOffset + 4)
				local B5 = buffer.readu8(Src, BOffset + 5)
				local B6 = buffer.readu8(Src, BOffset + 6)
				local B7 = buffer.readu8(Src, BOffset + 7)
				local B8 = buffer.readu8(Src, BOffset + 8)
				local B9 = buffer.readu8(Src, BOffset + 9)

				local V0 = bit32.bor(B0, bit32.lshift(B1, 8), bit32.lshift(bit32.band(B2, 0xF), 16))
				local V1 = bit32.bor(bit32.rshift(B2, 4), bit32.lshift(B3, 4), bit32.lshift(B4, 12))
				local V2 = bit32.bor(B5, bit32.lshift(B6, 8), bit32.lshift(bit32.band(B7, 0xF), 16))
				local V3 = bit32.bor(bit32.rshift(B7, 4), bit32.lshift(B8, 4), bit32.lshift(B9, 12))

				local D0 = G1 - V0
				local D1 = G1 - V1
				local D2 = G1 - V2
				local D3 = G1 - V3

				buffer.writeu32(Vec, POffset + 0, if D0 < 0 then D0 + Modulus else D0)
				buffer.writeu32(Vec, POffset + 4, if D1 < 0 then D1 + Modulus else D1)
				buffer.writeu32(Vec, POffset + 8, if D2 < 0 then D2 + Modulus else D2)
				buffer.writeu32(Vec, POffset + 12, if D3 < 0 then D3 + Modulus else D3)
			end
		end
	end
end

function Sampling.SampleInBall(Seed: buffer, Poly: buffer, Tau: number, Lambda: number)
	if not Params.CheckTau(Tau) then
		error("Invalid Tau parameter")
	end

	buffer.fill(Poly, 0, 0, N * 4)

	XOF.Reset256()
	XOF.Absorb256(Seed)

	local TauBits = XOF.Squeeze256(8)
	local TauBitsLow = buffer.readu32(TauBits, 0)
	local TauBitsHigh = buffer.readu32(TauBits, 4)

	local From = N - Tau
	local I = From
	local ChunkSize = 136
	local Modulus = Q

	while I < N do
		local RandomBytes = XOF.Squeeze256(ChunkSize)
		local ByteOffset = 0
		local OutputLen = buffer.len(RandomBytes)

		while I < N and ByteOffset + 3 < OutputLen do
			local Chunk = buffer.readu32(RandomBytes, ByteOffset)

			for B = 0, 3 do
				if I >= N then
					break
				end

				local J = bit32.band(bit32.rshift(Chunk, B * 8), 0xFF)

				if J <= I then
					local TauBit = I - From
					local S
					if TauBit < 32 then
						S = bit32.band(bit32.rshift(TauBitsLow, TauBit), 1)
					else
						S = bit32.band(bit32.rshift(TauBitsHigh, TauBit - 32), 1)
					end

					buffer.writeu32(Poly, I * 4, buffer.readu32(Poly, J * 4))
					buffer.writeu32(Poly, J * 4, if S == 0 then 1 else Modulus - 1)

					I += 1
				end
			end

			ByteOffset += 4
		end

		while I < N and ByteOffset < OutputLen do
			local J = buffer.readu8(RandomBytes, ByteOffset)

			if J <= I then
				local TauBit = I - From
				local S
				if TauBit < 32 then
					S = bit32.band(bit32.rshift(TauBitsLow, TauBit), 1)
				else
					S = bit32.band(bit32.rshift(TauBitsHigh, TauBit - 32), 1)
				end

				buffer.writeu32(Poly, I * 4, buffer.readu32(Poly, J * 4))
				buffer.writeu32(Poly, J * 4, if S == 0 then 1 else Modulus - 1)

				I += 1
			end

			ByteOffset += 1
		end
	end
end

return Sampling


--[=[
	Utility functions for ML-DSA key and signature sizes

	 ML-DSA parameters.

	Example usage:
		local Utils = require(script)

		local PubKeySize = Utils.PubKeyLen(4, 13)    -- 1312 bytes for ML-DSA-44
		local SecKeySize = Utils.SecKeyLen(4, 4, 2, 13)  -- 2560 bytes
		local SigSize = Utils.SigLen(4, 4, 131072, 80, 128)  -- Variable
--]=]

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

local Q_BIT_WIDTH = 23

local Utils = {}

local function BitWidth(Value: number): number
	if Value == 0 then
		return 0
	end

	local Width = 0
	local TempValue = Value

	while TempValue > 0 do
		Width += 1
		TempValue = bit32.rshift(TempValue, 1)
	end

	return Width
end

function Utils.PubKeyLen(K: number, D: number): number
	local T1Bw = Q_BIT_WIDTH - D
	local PkLen = 32 + K * 32 * T1Bw

	return PkLen
end

function Utils.SecKeyLen(K: number, L: number, Eta: number, D: number): number
	local EtaBw = BitWidth(2 * Eta)
	local SkLen = 32 + 32 + 64 + 32 * (EtaBw * (K + L) + K * D)

	return SkLen
end

function Utils.SigLen(K: number, L: number, Gamma1: number, Omega: number, Lambda: number): number
	local Gamma1Bw = BitWidth(Gamma1)
	local SigLen = math.floor((2 * Lambda) / 8) + (32 * L * Gamma1Bw) + (Omega + K)

	return SigLen
end

function Utils.BitWidth(Value: number): number
	return BitWidth(Value)
end

return Utils