local Array = require(".crypto.util.array")
local Bit = require(".crypto.util.bit")

local XOR = Bit.bxor

local SBOX = {
	[0] = 0x63,
	0x7C,
	0x77,
	0x7B,
	0xF2,
	0x6B,
	0x6F,
	0xC5,
	0x30,
	0x01,
	0x67,
	0x2B,
	0xFE,
	0xD7,
	0xAB,
	0x76,
	0xCA,
	0x82,
	0xC9,
	0x7D,
	0xFA,
	0x59,
	0x47,
	0xF0,
	0xAD,
	0xD4,
	0xA2,
	0xAF,
	0x9C,
	0xA4,
	0x72,
	0xC0,
	0xB7,
	0xFD,
	0x93,
	0x26,
	0x36,
	0x3F,
	0xF7,
	0xCC,
	0x34,
	0xA5,
	0xE5,
	0xF1,
	0x71,
	0xD8,
	0x31,
	0x15,
	0x04,
	0xC7,
	0x23,
	0xC3,
	0x18,
	0x96,
	0x05,
	0x9A,
	0x07,
	0x12,
	0x80,
	0xE2,
	0xEB,
	0x27,
	0xB2,
	0x75,
	0x09,
	0x83,
	0x2C,
	0x1A,
	0x1B,
	0x6E,
	0x5A,
	0xA0,
	0x52,
	0x3B,
	0xD6,
	0xB3,
	0x29,
	0xE3,
	0x2F,
	0x84,
	0x53,
	0xD1,
	0x00,
	0xED,
	0x20,
	0xFC,
	0xB1,
	0x5B,
	0x6A,
	0xCB,
	0xBE,
	0x39,
	0x4A,
	0x4C,
	0x58,
	0xCF,
	0xD0,
	0xEF,
	0xAA,
	0xFB,
	0x43,
	0x4D,
	0x33,
	0x85,
	0x45,
	0xF9,
	0x02,
	0x7F,
	0x50,
	0x3C,
	0x9F,
	0xA8,
	0x51,
	0xA3,
	0x40,
	0x8F,
	0x92,
	0x9D,
	0x38,
	0xF5,
	0xBC,
	0xB6,
	0xDA,
	0x21,
	0x10,
	0xFF,
	0xF3,
	0xD2,
	0xCD,
	0x0C,
	0x13,
	0xEC,
	0x5F,
	0x97,
	0x44,
	0x17,
	0xC4,
	0xA7,
	0x7E,
	0x3D,
	0x64,
	0x5D,
	0x19,
	0x73,
	0x60,
	0x81,
	0x4F,
	0xDC,
	0x22,
	0x2A,
	0x90,
	0x88,
	0x46,
	0xEE,
	0xB8,
	0x14,
	0xDE,
	0x5E,
	0x0B,
	0xDB,
	0xE0,
	0x32,
	0x3A,
	0x0A,
	0x49,
	0x06,
	0x24,
	0x5C,
	0xC2,
	0xD3,
	0xAC,
	0x62,
	0x91,
	0x95,
	0xE4,
	0x79,
	0xE7,
	0xC8,
	0x37,
	0x6D,
	0x8D,
	0xD5,
	0x4E,
	0xA9,
	0x6C,
	0x56,
	0xF4,
	0xEA,
	0x65,
	0x7A,
	0xAE,
	0x08,
	0xBA,
	0x78,
	0x25,
	0x2E,
	0x1C,
	0xA6,
	0xB4,
	0xC6,
	0xE8,
	0xDD,
	0x74,
	0x1F,
	0x4B,
	0xBD,
	0x8B,
	0x8A,
	0x70,
	0x3E,
	0xB5,
	0x66,
	0x48,
	0x03,
	0xF6,
	0x0E,
	0x61,
	0x35,
	0x57,
	0xB9,
	0x86,
	0xC1,
	0x1D,
	0x9E,
	0xE1,
	0xF8,
	0x98,
	0x11,
	0x69,
	0xD9,
	0x8E,
	0x94,
	0x9B,
	0x1E,
	0x87,
	0xE9,
	0xCE,
	0x55,
	0x28,
	0xDF,
	0x8C,
	0xA1,
	0x89,
	0x0D,
	0xBF,
	0xE6,
	0x42,
	0x68,
	0x41,
	0x99,
	0x2D,
	0x0F,
	0xB0,
	0x54,
	0xBB,
	0x16,
}

local ISBOX = {
	[0] = 0x52,
	0x09,
	0x6A,
	0xD5,
	0x30,
	0x36,
	0xA5,
	0x38,
	0xBF,
	0x40,
	0xA3,
	0x9E,
	0x81,
	0xF3,
	0xD7,
	0xFB,
	0x7C,
	0xE3,
	0x39,
	0x82,
	0x9B,
	0x2F,
	0xFF,
	0x87,
	0x34,
	0x8E,
	0x43,
	0x44,
	0xC4,
	0xDE,
	0xE9,
	0xCB,
	0x54,
	0x7B,
	0x94,
	0x32,
	0xA6,
	0xC2,
	0x23,
	0x3D,
	0xEE,
	0x4C,
	0x95,
	0x0B,
	0x42,
	0xFA,
	0xC3,
	0x4E,
	0x08,
	0x2E,
	0xA1,
	0x66,
	0x28,
	0xD9,
	0x24,
	0xB2,
	0x76,
	0x5B,
	0xA2,
	0x49,
	0x6D,
	0x8B,
	0xD1,
	0x25,
	0x72,
	0xF8,
	0xF6,
	0x64,
	0x86,
	0x68,
	0x98,
	0x16,
	0xD4,
	0xA4,
	0x5C,
	0xCC,
	0x5D,
	0x65,
	0xB6,
	0x92,
	0x6C,
	0x70,
	0x48,
	0x50,
	0xFD,
	0xED,
	0xB9,
	0xDA,
	0x5E,
	0x15,
	0x46,
	0x57,
	0xA7,
	0x8D,
	0x9D,
	0x84,
	0x90,
	0xD8,
	0xAB,
	0x00,
	0x8C,
	0xBC,
	0xD3,
	0x0A,
	0xF7,
	0xE4,
	0x58,
	0x05,
	0xB8,
	0xB3,
	0x45,
	0x06,
	0xD0,
	0x2C,
	0x1E,
	0x8F,
	0xCA,
	0x3F,
	0x0F,
	0x02,
	0xC1,
	0xAF,
	0xBD,
	0x03,
	0x01,
	0x13,
	0x8A,
	0x6B,
	0x3A,
	0x91,
	0x11,
	0x41,
	0x4F,
	0x67,
	0xDC,
	0xEA,
	0x97,
	0xF2,
	0xCF,
	0xCE,
	0xF0,
	0xB4,
	0xE6,
	0x73,
	0x96,
	0xAC,
	0x74,
	0x22,
	0xE7,
	0xAD,
	0x35,
	0x85,
	0xE2,
	0xF9,
	0x37,
	0xE8,
	0x1C,
	0x75,
	0xDF,
	0x6E,
	0x47,
	0xF1,
	0x1A,
	0x71,
	0x1D,
	0x29,
	0xC5,
	0x89,
	0x6F,
	0xB7,
	0x62,
	0x0E,
	0xAA,
	0x18,
	0xBE,
	0x1B,
	0xFC,
	0x56,
	0x3E,
	0x4B,
	0xC6,
	0xD2,
	0x79,
	0x20,
	0x9A,
	0xDB,
	0xC0,
	0xFE,
	0x78,
	0xCD,
	0x5A,
	0xF4,
	0x1F,
	0xDD,
	0xA8,
	0x33,
	0x88,
	0x07,
	0xC7,
	0x31,
	0xB1,
	0x12,
	0x10,
	0x59,
	0x27,
	0x80,
	0xEC,
	0x5F,
	0x60,
	0x51,
	0x7F,
	0xA9,
	0x19,
	0xB5,
	0x4A,
	0x0D,
	0x2D,
	0xE5,
	0x7A,
	0x9F,
	0x93,
	0xC9,
	0x9C,
	0xEF,
	0xA0,
	0xE0,
	0x3B,
	0x4D,
	0xAE,
	0x2A,
	0xF5,
	0xB0,
	0xC8,
	0xEB,
	0xBB,
	0x3C,
	0x83,
	0x53,
	0x99,
	0x61,
	0x17,
	0x2B,
	0x04,
	0x7E,
	0xBA,
	0x77,
	0xD6,
	0x26,
	0xE1,
	0x69,
	0x14,
	0x63,
	0x55,
	0x21,
	0x0C,
	0x7D,
}

local ROW_SHIFT = { 1, 6, 11, 16, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12 }
local IROW_SHIFT = { 1, 14, 11, 8, 5, 2, 15, 12, 9, 6, 3, 16, 13, 10, 7, 4 }

local ETABLE = {
	[0] = 0x01,
	0x03,
	0x05,
	0x0F,
	0x11,
	0x33,
	0x55,
	0xFF,
	0x1A,
	0x2E,
	0x72,
	0x96,
	0xA1,
	0xF8,
	0x13,
	0x35,
	0x5F,
	0xE1,
	0x38,
	0x48,
	0xD8,
	0x73,
	0x95,
	0xA4,
	0xF7,
	0x02,
	0x06,
	0x0A,
	0x1E,
	0x22,
	0x66,
	0xAA,
	0xE5,
	0x34,
	0x5C,
	0xE4,
	0x37,
	0x59,
	0xEB,
	0x26,
	0x6A,
	0xBE,
	0xD9,
	0x70,
	0x90,
	0xAB,
	0xE6,
	0x31,
	0x53,
	0xF5,
	0x04,
	0x0C,
	0x14,
	0x3C,
	0x44,
	0xCC,
	0x4F,
	0xD1,
	0x68,
	0xB8,
	0xD3,
	0x6E,
	0xB2,
	0xCD,
	0x4C,
	0xD4,
	0x67,
	0xA9,
	0xE0,
	0x3B,
	0x4D,
	0xD7,
	0x62,
	0xA6,
	0xF1,
	0x08,
	0x18,
	0x28,
	0x78,
	0x88,
	0x83,
	0x9E,
	0xB9,
	0xD0,
	0x6B,
	0xBD,
	0xDC,
	0x7F,
	0x81,
	0x98,
	0xB3,
	0xCE,
	0x49,
	0xDB,
	0x76,
	0x9A,
	0xB5,
	0xC4,
	0x57,
	0xF9,
	0x10,
	0x30,
	0x50,
	0xF0,
	0x0B,
	0x1D,
	0x27,
	0x69,
	0xBB,
	0xD6,
	0x61,
	0xA3,
	0xFE,
	0x19,
	0x2B,
	0x7D,
	0x87,
	0x92,
	0xAD,
	0xEC,
	0x2F,
	0x71,
	0x93,
	0xAE,
	0xE9,
	0x20,
	0x60,
	0xA0,
	0xFB,
	0x16,
	0x3A,
	0x4E,
	0xD2,
	0x6D,
	0xB7,
	0xC2,
	0x5D,
	0xE7,
	0x32,
	0x56,
	0xFA,
	0x15,
	0x3F,
	0x41,
	0xC3,
	0x5E,
	0xE2,
	0x3D,
	0x47,
	0xC9,
	0x40,
	0xC0,
	0x5B,
	0xED,
	0x2C,
	0x74,
	0x9C,
	0xBF,
	0xDA,
	0x75,
	0x9F,
	0xBA,
	0xD5,
	0x64,
	0xAC,
	0xEF,
	0x2A,
	0x7E,
	0x82,
	0x9D,
	0xBC,
	0xDF,
	0x7A,
	0x8E,
	0x89,
	0x80,
	0x9B,
	0xB6,
	0xC1,
	0x58,
	0xE8,
	0x23,
	0x65,
	0xAF,
	0xEA,
	0x25,
	0x6F,
	0xB1,
	0xC8,
	0x43,
	0xC5,
	0x54,
	0xFC,
	0x1F,
	0x21,
	0x63,
	0xA5,
	0xF4,
	0x07,
	0x09,
	0x1B,
	0x2D,
	0x77,
	0x99,
	0xB0,
	0xCB,
	0x46,
	0xCA,
	0x45,
	0xCF,
	0x4A,
	0xDE,
	0x79,
	0x8B,
	0x86,
	0x91,
	0xA8,
	0xE3,
	0x3E,
	0x42,
	0xC6,
	0x51,
	0xF3,
	0x0E,
	0x12,
	0x36,
	0x5A,
	0xEE,
	0x29,
	0x7B,
	0x8D,
	0x8C,
	0x8F,
	0x8A,
	0x85,
	0x94,
	0xA7,
	0xF2,
	0x0D,
	0x17,
	0x39,
	0x4B,
	0xDD,
	0x7C,
	0x84,
	0x97,
	0xA2,
	0xFD,
	0x1C,
	0x24,
	0x6C,
	0xB4,
	0xC7,
	0x52,
	0xF6,
	0x01,
}

local LTABLE = {
	[0] = 0x00,
	0x00,
	0x19,
	0x01,
	0x32,
	0x02,
	0x1A,
	0xC6,
	0x4B,
	0xC7,
	0x1B,
	0x68,
	0x33,
	0xEE,
	0xDF,
	0x03,
	0x64,
	0x04,
	0xE0,
	0x0E,
	0x34,
	0x8D,
	0x81,
	0xEF,
	0x4C,
	0x71,
	0x08,
	0xC8,
	0xF8,
	0x69,
	0x1C,
	0xC1,
	0x7D,
	0xC2,
	0x1D,
	0xB5,
	0xF9,
	0xB9,
	0x27,
	0x6A,
	0x4D,
	0xE4,
	0xA6,
	0x72,
	0x9A,
	0xC9,
	0x09,
	0x78,
	0x65,
	0x2F,
	0x8A,
	0x05,
	0x21,
	0x0F,
	0xE1,
	0x24,
	0x12,
	0xF0,
	0x82,
	0x45,
	0x35,
	0x93,
	0xDA,
	0x8E,
	0x96,
	0x8F,
	0xDB,
	0xBD,
	0x36,
	0xD0,
	0xCE,
	0x94,
	0x13,
	0x5C,
	0xD2,
	0xF1,
	0x40,
	0x46,
	0x83,
	0x38,
	0x66,
	0xDD,
	0xFD,
	0x30,
	0xBF,
	0x06,
	0x8B,
	0x62,
	0xB3,
	0x25,
	0xE2,
	0x98,
	0x22,
	0x88,
	0x91,
	0x10,
	0x7E,
	0x6E,
	0x48,
	0xC3,
	0xA3,
	0xB6,
	0x1E,
	0x42,
	0x3A,
	0x6B,
	0x28,
	0x54,
	0xFA,
	0x85,
	0x3D,
	0xBA,
	0x2B,
	0x79,
	0x0A,
	0x15,
	0x9B,
	0x9F,
	0x5E,
	0xCA,
	0x4E,
	0xD4,
	0xAC,
	0xE5,
	0xF3,
	0x73,
	0xA7,
	0x57,
	0xAF,
	0x58,
	0xA8,
	0x50,
	0xF4,
	0xEA,
	0xD6,
	0x74,
	0x4F,
	0xAE,
	0xE9,
	0xD5,
	0xE7,
	0xE6,
	0xAD,
	0xE8,
	0x2C,
	0xD7,
	0x75,
	0x7A,
	0xEB,
	0x16,
	0x0B,
	0xF5,
	0x59,
	0xCB,
	0x5F,
	0xB0,
	0x9C,
	0xA9,
	0x51,
	0xA0,
	0x7F,
	0x0C,
	0xF6,
	0x6F,
	0x17,
	0xC4,
	0x49,
	0xEC,
	0xD8,
	0x43,
	0x1F,
	0x2D,
	0xA4,
	0x76,
	0x7B,
	0xB7,
	0xCC,
	0xBB,
	0x3E,
	0x5A,
	0xFB,
	0x60,
	0xB1,
	0x86,
	0x3B,
	0x52,
	0xA1,
	0x6C,
	0xAA,
	0x55,
	0x29,
	0x9D,
	0x97,
	0xB2,
	0x87,
	0x90,
	0x61,
	0xBE,
	0xDC,
	0xFC,
	0xBC,
	0x95,
	0xCF,
	0xCD,
	0x37,
	0x3F,
	0x5B,
	0xD1,
	0x53,
	0x39,
	0x84,
	0x3C,
	0x41,
	0xA2,
	0x6D,
	0x47,
	0x14,
	0x2A,
	0x9E,
	0x5D,
	0x56,
	0xF2,
	0xD3,
	0xAB,
	0x44,
	0x11,
	0x92,
	0xD9,
	0x23,
	0x20,
	0x2E,
	0x89,
	0xB4,
	0x7C,
	0xB8,
	0x26,
	0x77,
	0x99,
	0xE3,
	0xA5,
	0x67,
	0x4A,
	0xED,
	0xDE,
	0xC5,
	0x31,
	0xFE,
	0x18,
	0x0D,
	0x63,
	0x8C,
	0x80,
	0xC0,
	0xF7,
	0x70,
	0x07,
}

local MIXTABLE = {
	0x02,
	0x03,
	0x01,
	0x01,
	0x01,
	0x02,
	0x03,
	0x01,
	0x01,
	0x01,
	0x02,
	0x03,
	0x03,
	0x01,
	0x01,
	0x02,
}

local IMIXTABLE = {
	0x0E,
	0x0B,
	0x0D,
	0x09,
	0x09,
	0x0E,
	0x0B,
	0x0D,
	0x0D,
	0x09,
	0x0E,
	0x0B,
	0x0B,
	0x0D,
	0x09,
	0x0E,
}

local RCON = {
	[0] = 0x8d,
	0x01,
	0x02,
	0x04,
	0x08,
	0x10,
	0x20,
	0x40,
	0x80,
	0x1b,
	0x36,
	0x6c,
	0xd8,
	0xab,
	0x4d,
	0x9a,
	0x2f,
	0x5e,
	0xbc,
	0x63,
	0xc6,
	0x97,
	0x35,
	0x6a,
	0xd4,
	0xb3,
	0x7d,
	0xfa,
	0xef,
	0xc5,
	0x91,
	0x39,
	0x72,
	0xe4,
	0xd3,
	0xbd,
	0x61,
	0xc2,
	0x9f,
	0x25,
	0x4a,
	0x94,
	0x33,
	0x66,
	0xcc,
	0x83,
	0x1d,
	0x3a,
	0x74,
	0xe8,
	0xcb,
	0x8d,
	0x01,
	0x02,
	0x04,
	0x08,
	0x10,
	0x20,
	0x40,
	0x80,
	0x1b,
	0x36,
	0x6c,
	0xd8,
	0xab,
	0x4d,
	0x9a,
	0x2f,
	0x5e,
	0xbc,
	0x63,
	0xc6,
	0x97,
	0x35,
	0x6a,
	0xd4,
	0xb3,
	0x7d,
	0xfa,
	0xef,
	0xc5,
	0x91,
	0x39,
	0x72,
	0xe4,
	0xd3,
	0xbd,
	0x61,
	0xc2,
	0x9f,
	0x25,
	0x4a,
	0x94,
	0x33,
	0x66,
	0xcc,
	0x83,
	0x1d,
	0x3a,
	0x74,
	0xe8,
	0xcb,
	0x8d,
	0x01,
	0x02,
	0x04,
	0x08,
	0x10,
	0x20,
	0x40,
	0x80,
	0x1b,
	0x36,
	0x6c,
	0xd8,
	0xab,
	0x4d,
	0x9a,
	0x2f,
	0x5e,
	0xbc,
	0x63,
	0xc6,
	0x97,
	0x35,
	0x6a,
	0xd4,
	0xb3,
	0x7d,
	0xfa,
	0xef,
	0xc5,
	0x91,
	0x39,
	0x72,
	0xe4,
	0xd3,
	0xbd,
	0x61,
	0xc2,
	0x9f,
	0x25,
	0x4a,
	0x94,
	0x33,
	0x66,
	0xcc,
	0x83,
	0x1d,
	0x3a,
	0x74,
	0xe8,
	0xcb,
	0x8d,
	0x01,
	0x02,
	0x04,
	0x08,
	0x10,
	0x20,
	0x40,
	0x80,
	0x1b,
	0x36,
	0x6c,
	0xd8,
	0xab,
	0x4d,
	0x9a,
	0x2f,
	0x5e,
	0xbc,
	0x63,
	0xc6,
	0x97,
	0x35,
	0x6a,
	0xd4,
	0xb3,
	0x7d,
	0xfa,
	0xef,
	0xc5,
	0x91,
	0x39,
	0x72,
	0xe4,
	0xd3,
	0xbd,
	0x61,
	0xc2,
	0x9f,
	0x25,
	0x4a,
	0x94,
	0x33,
	0x66,
	0xcc,
	0x83,
	0x1d,
	0x3a,
	0x74,
	0xe8,
	0xcb,
	0x8d,
	0x01,
	0x02,
	0x04,
	0x08,
	0x10,
	0x20,
	0x40,
	0x80,
	0x1b,
	0x36,
	0x6c,
	0xd8,
	0xab,
	0x4d,
	0x9a,
	0x2f,
	0x5e,
	0xbc,
	0x63,
	0xc6,
	0x97,
	0x35,
	0x6a,
	0xd4,
	0xb3,
	0x7d,
	0xfa,
	0xef,
	0xc5,
	0x91,
	0x39,
	0x72,
	0xe4,
	0xd3,
	0xbd,
	0x61,
	0xc2,
	0x9f,
	0x25,
	0x4a,
	0x94,
	0x33,
	0x66,
	0xcc,
	0x83,
	0x1d,
	0x3a,
	0x74,
	0xe8,
	0xcb,
	0x8d,
}

local GMUL = function(A, B)
	if A == 0x01 then
		return B
	end
	if B == 0x01 then
		return A
	end
	if A == 0x00 then
		return 0
	end
	if B == 0x00 then
		return 0
	end

	local LA = LTABLE[A]
	local LB = LTABLE[B]

	local sum = LA + LB
	if sum > 0xFF then
		sum = sum - 0xFF
	end

	return ETABLE[sum]
end

local byteSub = Array.substitute

local shiftRow = Array.permute

local mixCol = function(i, mix)
	local out = {}

	local a, b, c, d

	a = GMUL(i[1], mix[1])
	b = GMUL(i[2], mix[2])
	c = GMUL(i[3], mix[3])
	d = GMUL(i[4], mix[4])
	out[1] = XOR(XOR(a, b), XOR(c, d))
	a = GMUL(i[1], mix[5])
	b = GMUL(i[2], mix[6])
	c = GMUL(i[3], mix[7])
	d = GMUL(i[4], mix[8])
	out[2] = XOR(XOR(a, b), XOR(c, d))
	a = GMUL(i[1], mix[9])
	b = GMUL(i[2], mix[10])
	c = GMUL(i[3], mix[11])
	d = GMUL(i[4], mix[12])
	out[3] = XOR(XOR(a, b), XOR(c, d))
	a = GMUL(i[1], mix[13])
	b = GMUL(i[2], mix[14])
	c = GMUL(i[3], mix[15])
	d = GMUL(i[4], mix[16])
	out[4] = XOR(XOR(a, b), XOR(c, d))

	a = GMUL(i[5], mix[1])
	b = GMUL(i[6], mix[2])
	c = GMUL(i[7], mix[3])
	d = GMUL(i[8], mix[4])
	out[5] = XOR(XOR(a, b), XOR(c, d))
	a = GMUL(i[5], mix[5])
	b = GMUL(i[6], mix[6])
	c = GMUL(i[7], mix[7])
	d = GMUL(i[8], mix[8])
	out[6] = XOR(XOR(a, b), XOR(c, d))
	a = GMUL(i[5], mix[9])
	b = GMUL(i[6], mix[10])
	c = GMUL(i[7], mix[11])
	d = GMUL(i[8], mix[12])
	out[7] = XOR(XOR(a, b), XOR(c, d))
	a = GMUL(i[5], mix[13])
	b = GMUL(i[6], mix[14])
	c = GMUL(i[7], mix[15])
	d = GMUL(i[8], mix[16])
	out[8] = XOR(XOR(a, b), XOR(c, d))

	a = GMUL(i[9], mix[1])
	b = GMUL(i[10], mix[2])
	c = GMUL(i[11], mix[3])
	d = GMUL(i[12], mix[4])
	out[9] = XOR(XOR(a, b), XOR(c, d))
	a = GMUL(i[9], mix[5])
	b = GMUL(i[10], mix[6])
	c = GMUL(i[11], mix[7])
	d = GMUL(i[12], mix[8])
	out[10] = XOR(XOR(a, b), XOR(c, d))
	a = GMUL(i[9], mix[9])
	b = GMUL(i[10], mix[10])
	c = GMUL(i[11], mix[11])
	d = GMUL(i[12], mix[12])
	out[11] = XOR(XOR(a, b), XOR(c, d))
	a = GMUL(i[9], mix[13])
	b = GMUL(i[10], mix[14])
	c = GMUL(i[11], mix[15])
	d = GMUL(i[12], mix[16])
	out[12] = XOR(XOR(a, b), XOR(c, d))

	a = GMUL(i[13], mix[1])
	b = GMUL(i[14], mix[2])
	c = GMUL(i[15], mix[3])
	d = GMUL(i[16], mix[4])
	out[13] = XOR(XOR(a, b), XOR(c, d))
	a = GMUL(i[13], mix[5])
	b = GMUL(i[14], mix[6])
	c = GMUL(i[15], mix[7])
	d = GMUL(i[16], mix[8])
	out[14] = XOR(XOR(a, b), XOR(c, d))
	a = GMUL(i[13], mix[9])
	b = GMUL(i[14], mix[10])
	c = GMUL(i[15], mix[11])
	d = GMUL(i[16], mix[12])
	out[15] = XOR(XOR(a, b), XOR(c, d))
	a = GMUL(i[13], mix[13])
	b = GMUL(i[14], mix[14])
	c = GMUL(i[15], mix[15])
	d = GMUL(i[16], mix[16])
	out[16] = XOR(XOR(a, b), XOR(c, d))

	return out
end

local keyRound = function(key, round)
	local out = {}

	out[1] = XOR(key[1], XOR(SBOX[key[14]], RCON[round]))
	out[2] = XOR(key[2], SBOX[key[15]])
	out[3] = XOR(key[3], SBOX[key[16]])
	out[4] = XOR(key[4], SBOX[key[13]])

	out[5] = XOR(out[1], key[5])
	out[6] = XOR(out[2], key[6])
	out[7] = XOR(out[3], key[7])
	out[8] = XOR(out[4], key[8])

	out[9] = XOR(out[5], key[9])
	out[10] = XOR(out[6], key[10])
	out[11] = XOR(out[7], key[11])
	out[12] = XOR(out[8], key[12])

	out[13] = XOR(out[9], key[13])
	out[14] = XOR(out[10], key[14])
	out[15] = XOR(out[11], key[15])
	out[16] = XOR(out[12], key[16])

	return out
end

local keyExpand = function(key)
	local keys = {}

	local temp = key

	keys[1] = temp

	for i = 1, 10 do
		temp = keyRound(temp, i)
		keys[i + 1] = temp
	end

	return keys
end

local addKey = Array.XOR

local AES = {}

AES.blockSize = 16

AES.encrypt = function(key, block)
	local keySchedule = keyExpand(key)

	--round 0
	block = addKey(block, keySchedule[1])

	--round 1
	block = byteSub(block, SBOX)
	block = shiftRow(block, ROW_SHIFT)
	block = mixCol(block, MIXTABLE)
	block = addKey(block, keySchedule[2])

	--round 2
	block = byteSub(block, SBOX)
	block = shiftRow(block, ROW_SHIFT)
	block = mixCol(block, MIXTABLE)
	block = addKey(block, keySchedule[3])

	--round 3
	block = byteSub(block, SBOX)
	block = shiftRow(block, ROW_SHIFT)
	block = mixCol(block, MIXTABLE)
	block = addKey(block, keySchedule[4])

	--round 4
	block = byteSub(block, SBOX)
	block = shiftRow(block, ROW_SHIFT)
	block = mixCol(block, MIXTABLE)
	block = addKey(block, keySchedule[5])

	--round 5
	block = byteSub(block, SBOX)
	block = shiftRow(block, ROW_SHIFT)
	block = mixCol(block, MIXTABLE)
	block = addKey(block, keySchedule[6])

	--round 6
	block = byteSub(block, SBOX)
	block = shiftRow(block, ROW_SHIFT)
	block = mixCol(block, MIXTABLE)
	block = addKey(block, keySchedule[7])

	--round 7
	block = byteSub(block, SBOX)
	block = shiftRow(block, ROW_SHIFT)
	block = mixCol(block, MIXTABLE)
	block = addKey(block, keySchedule[8])

	--round 8
	block = byteSub(block, SBOX)
	block = shiftRow(block, ROW_SHIFT)
	block = mixCol(block, MIXTABLE)
	block = addKey(block, keySchedule[9])

	--round 9
	block = byteSub(block, SBOX)
	block = shiftRow(block, ROW_SHIFT)
	block = mixCol(block, MIXTABLE)
	block = addKey(block, keySchedule[10])

	--round 10
	block = byteSub(block, SBOX)
	block = shiftRow(block, ROW_SHIFT)
	block = addKey(block, keySchedule[11])

	return block
end

AES.decrypt = function(key, block)
	local keySchedule = keyExpand(key)

	--round 0
	block = addKey(block, keySchedule[11])

	--round 1
	block = shiftRow(block, IROW_SHIFT)
	block = byteSub(block, ISBOX)
	block = addKey(block, keySchedule[10])
	block = mixCol(block, IMIXTABLE)

	--round 2
	block = shiftRow(block, IROW_SHIFT)
	block = byteSub(block, ISBOX)
	block = addKey(block, keySchedule[9])
	block = mixCol(block, IMIXTABLE)

	--round 3
	block = shiftRow(block, IROW_SHIFT)
	block = byteSub(block, ISBOX)
	block = addKey(block, keySchedule[8])
	block = mixCol(block, IMIXTABLE)

	--round 4
	block = shiftRow(block, IROW_SHIFT)
	block = byteSub(block, ISBOX)
	block = addKey(block, keySchedule[7])
	block = mixCol(block, IMIXTABLE)

	--round 5
	block = shiftRow(block, IROW_SHIFT)
	block = byteSub(block, ISBOX)
	block = addKey(block, keySchedule[6])
	block = mixCol(block, IMIXTABLE)

	--round 6
	block = shiftRow(block, IROW_SHIFT)
	block = byteSub(block, ISBOX)
	block = addKey(block, keySchedule[5])
	block = mixCol(block, IMIXTABLE)

	--round 7
	block = shiftRow(block, IROW_SHIFT)
	block = byteSub(block, ISBOX)
	block = addKey(block, keySchedule[4])
	block = mixCol(block, IMIXTABLE)

	--round 8
	block = shiftRow(block, IROW_SHIFT)
	block = byteSub(block, ISBOX)
	block = addKey(block, keySchedule[3])
	block = mixCol(block, IMIXTABLE)

	--round 9
	block = shiftRow(block, IROW_SHIFT)
	block = byteSub(block, ISBOX)
	block = addKey(block, keySchedule[2])
	block = mixCol(block, IMIXTABLE)

	--round 10
	block = shiftRow(block, IROW_SHIFT)
	block = byteSub(block, ISBOX)
	block = addKey(block, keySchedule[1])

	return block
end

return AES
