package aes

import (
	"encoding/hex"
	"fmt"
	"log"
	"strconv"
)

var sbox = [][]byte{
	{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},
}

var invSbox = [][]byte{
	{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},
}

var mm = []byte{
	2, 3, 1, 1,
	1, 2, 3, 1,
	1, 1, 2, 3,
	3, 1, 1, 2,
}

var iMM = []byte{
	14, 11, 13, 9,
	9, 14, 11, 13,
	13, 9, 14, 11,
	11, 13, 9, 14,
}

var rcon = []byte{0x00, // rcon[] is 1-based, so the first entry is just a place holder
	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,
}

//Block renamed []byte type for simplicity
type Block []byte

var keyexpanded []Block
var key Block

//ToString writes []Block to a string in ascii form
func ToString(all []Block, keysize int, k Block, decrypt bool) string {
	final := ""
	for _, bl := range all {
		result := Block{}
		if decrypt {
			result = InvCipher(bl, keysize, k)
		} else {
			result = Cipher(bl, keysize, k)
		}
		final += string(result)
	}
	return final
}

//ToHex writes []Block to a string in hex form
func ToHex(all []Block, keysize int, k Block, decrypt bool) string {
	final := ""
	for _, bl := range all {
		result := Block{}
		if decrypt {
			result = InvCipher(bl, keysize, k)
		} else {
			result = Cipher(bl, keysize, k)
		}
		for i := 0; i < 16; i++ {
			final += fmt.Sprintf("0x%x ", result[i])
		}
	}
	return final
}

//BlockGen helps create []Block from incoming string
func BlockGen(arg string) []Block {
	all := []Block{}
	b := Block{}
	for i, char := range arg {
		value, err := strconv.ParseUint(
			hex.EncodeToString(
				[]byte(string(char))),
			16,
			32,
		)
		if err != nil {
			log.Fatal(err)
		}
		if i%16 == 0 && i > 0 {
			all = append(all, b)
			b = b[:0]
		}
		b = append(b, byte(value))
		if i == len(arg)-1 {
			all = append(all, b)
		}
	}
	return all
}

//Cipher performs AES cipher
func Cipher(cur Block, bit int, incomingKey Block) Block {
	if len(cur) != 16 {
		missing := 16 - len(cur)
		for i := 0; i < missing; i++ {
			cur = append(cur, 0x00)
		}
	}
	key = Block{}
	keyexpanded = []Block{}
	assignKey(incomingKey)
	if bit == 128 {
		keyExpansionBase(128)
		cur = addRoundKey(cur, 0)
		for i := 0; i < 9; i++ {
			cur = subBytes(cur)
			cur = shiftRows(cur)
			cur = mixColumns(cur)
			cur = addRoundKey(cur, i+1)
		}
		cur = subBytes(cur)
		cur = shiftRows(cur)
		cur = addRoundKey(cur, 10)
		return cur
	}
	if bit == 192 {
		keyExpansionBase(192)
		cur = addRoundKey(cur, 0)
		for i := 0; i < 11; i++ {
			cur = subBytes(cur)
			cur = shiftRows(cur)
			cur = mixColumns(cur)
			cur = addRoundKey(cur, i+1)
			if i == 0 {
			}
		}
		cur = subBytes(cur)
		cur = shiftRows(cur)
		cur = addRoundKey(cur, 12)
		return cur
	}
	if bit == 256 {
		keyExpansionBase(256)
		cur = addRoundKey(cur, 0)
		for i := 0; i < 13; i++ {
			cur = subBytes(cur)
			cur = shiftRows(cur)
			cur = mixColumns(cur)
			cur = addRoundKey(cur, i+1)
			if i == 0 {
			}
		}
		cur = subBytes(cur)
		cur = shiftRows(cur)
		cur = addRoundKey(cur, 14)
		return cur
	}
	return cur
}

//InvCipher inverse AES cipher
func InvCipher(cur Block, bit int, incomingKey Block) Block {
	if len(cur) != 16 {
		missing := 16 - len(cur)
		for i := 0; i < missing; i++ {
			cur = append(cur, 0x00)
		}
	}
	key = Block{}
	keyexpanded = []Block{}
	assignKey(incomingKey)
	if bit == 128 {
		keyExpansionBase(128)
		cur = addRoundKey(cur, 10)
		for i := 9; i > 0; i-- {
			cur = invShiftRows(cur)
			cur = invSubBytes(cur)
			cur = addRoundKey(cur, i)
			cur = invMixColumns(cur)
		}
		cur = invSubBytes(cur)
		cur = invShiftRows(cur)
		cur = addRoundKey(cur, 0)
		return cur
	}
	if bit == 192 {
		keyExpansionBase(192)
		cur = addRoundKey(cur, 12)
		for i := 11; i > 0; i-- {
			cur = invShiftRows(cur)
			cur = invSubBytes(cur)
			cur = addRoundKey(cur, i)
			cur = invMixColumns(cur)
		}
		cur = invSubBytes(cur)
		cur = invShiftRows(cur)
		cur = addRoundKey(cur, 0)
		return cur
	}
	if bit == 256 {
		keyExpansionBase(256)
		cur = addRoundKey(cur, 14)
		for i := 13; i > 0; i-- {
			cur = invShiftRows(cur)
			cur = invSubBytes(cur)
			cur = addRoundKey(cur, i)
			cur = invMixColumns(cur)
		}
		cur = invSubBytes(cur)
		cur = invShiftRows(cur)
		cur = addRoundKey(cur, 0)
		return cur
	}
	return cur
}

func addRoundKey(cur Block, iteration int) Block {
	for i := 0; i < 16; i++ {
		cur[i] = cur[i] ^ keyexpanded[iteration][i]
	}
	return cur
}

func assignKey(cur Block) {
	key = cur
}

func keyExpansionBase(keysize int) {
	if keysize == 128 {
		keyexpanded = append(keyexpanded, key)
		for i := 0; i < 10; i++ {
			keyExpansion(keyexpanded[i], i+1)
		}
	} else if keysize == 192 {
		keyexpanded = append(keyexpanded, key)
		for i := 0; i < 8; i++ {
			keyExpansion192(keyexpanded[i], i+1)
		}
		temp := keyexpanded
		keyexpanded = []Block{}
		for i := 0; i < 9; i++ {
			if i == 8 {
				a := Block{
					temp[i][0], temp[i][1], temp[i][2], temp[i][3],
					temp[i][6], temp[i][7], temp[i][8], temp[i][9],
					temp[i][12], temp[i][13], temp[i][14], temp[i][15],
					temp[i][18], temp[i][19], temp[i][20], temp[i][21],
				}
				b := Block{
					temp[i][4], temp[i][5], 0, 0,
					temp[i][10], temp[i][11], 0, 0,
					temp[i][16], temp[i][17], 0, 0,
					temp[i][22], temp[i][23], 0, 0,
				}
				keyexpanded = append(keyexpanded, a)
				keyexpanded = append(keyexpanded, b)
			} else {
				a := Block{
					temp[i][0], temp[i][1], temp[i][2], temp[i][3],
					temp[i][6], temp[i][7], temp[i][8], temp[i][9],
					temp[i][12], temp[i][13], temp[i][14], temp[i][15],
					temp[i][18], temp[i][19], temp[i][20], temp[i][21],
				}
				b := Block{
					temp[i][4], temp[i][5], temp[i+1][0], temp[i+1][1],
					temp[i][10], temp[i][11], temp[i+1][6], temp[i+1][7],
					temp[i][16], temp[i][17], temp[i+1][12], temp[i+1][13],
					temp[i][22], temp[i][23], temp[i+1][18], temp[i+1][19],
				}
				c := Block{
					temp[i+1][2], temp[i+1][3], temp[i+1][4], temp[i+1][5],
					temp[i+1][8], temp[i+1][9], temp[i+1][10], temp[i+1][11],
					temp[i+1][14], temp[i+1][15], temp[i+1][16], temp[i+1][17],
					temp[i+1][20], temp[i+1][21], temp[i+1][22], temp[i+1][23],
				}
				keyexpanded = append(keyexpanded, a)
				keyexpanded = append(keyexpanded, b)
				keyexpanded = append(keyexpanded, c)
			}
			i++
		}
	} else if keysize == 256 {
		keyexpanded = append(keyexpanded, key)
		for i := 0; i < 8; i++ {
			keyExpansion256(keyexpanded[i], i+1)
		}
		temp := keyexpanded
		keyexpanded = []Block{}
		for i := 0; i < 9; i++ {
			a := Block{
				temp[i][0], temp[i][1], temp[i][2], temp[i][3],
				temp[i][8], temp[i][9], temp[i][10], temp[i][11],
				temp[i][16], temp[i][17], temp[i][18], temp[i][19],
				temp[i][24], temp[i][25], temp[i][26], temp[i][27],
			}
			b := Block{
				temp[i][4], temp[i][5], temp[i][6], temp[i][7],
				temp[i][12], temp[i][13], temp[i][14], temp[i][15],
				temp[i][20], temp[i][21], temp[i][22], temp[i][23],
				temp[i][28], temp[i][29], temp[i][30], temp[i][31],
			}
			keyexpanded = append(keyexpanded, a)
			keyexpanded = append(keyexpanded, b)
		}
	}
}

func keyExpansion(cur Block, iteration int) Block {
	var nb = Block{ //nb = nextBlock
		0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00,
	}
	rotword := []byte{cur[7], cur[11], cur[15], cur[3]}
	for i := 0; i < 4; i++ {
		upper, lower := splitBytes(rotword[i])
		rotword[i] = sbox[upper][lower]
	}
	rotword[0] = rotword[0] ^ cur[0] ^ rcon[iteration]
	rotword[1] = rotword[1] ^ cur[4]
	rotword[2] = rotword[2] ^ cur[8]
	rotword[3] = rotword[3] ^ cur[12]

	nb[0], nb[4], nb[8], nb[12] = rotword[0], rotword[1], rotword[2], rotword[3]
	nb[1], nb[5], nb[9], nb[13] = nb[0]^cur[1], nb[4]^cur[5], nb[8]^cur[9], nb[12]^cur[13]
	nb[2], nb[6], nb[10], nb[14] = nb[1]^cur[2], nb[5]^cur[6], nb[9]^cur[10], nb[13]^cur[14]
	nb[3], nb[7], nb[11], nb[15] = nb[2]^cur[3], nb[6]^cur[7], nb[10]^cur[11], nb[14]^cur[15]
	keyexpanded = append(keyexpanded, nb)
	return nb
}

func keyExpansion192(cur Block, iteration int) Block {
	var nb = Block{ //nb = nextBlock
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	}
	rotword := []byte{cur[11], cur[17], cur[23], cur[5]}
	for i := 0; i < 4; i++ {
		upper, lower := splitBytes(rotword[i])
		rotword[i] = sbox[upper][lower]
	}
	rotword[0] = rotword[0] ^ cur[0] ^ rcon[iteration]
	rotword[1] = rotword[1] ^ cur[6]
	rotword[2] = rotword[2] ^ cur[12]
	rotword[3] = rotword[3] ^ cur[18]

	nb[0], nb[6], nb[12], nb[18] = rotword[0], rotword[1], rotword[2], rotword[3]
	nb[1], nb[7], nb[13], nb[19] = nb[0]^cur[1], nb[6]^cur[7], nb[12]^cur[13], nb[18]^cur[19]
	nb[2], nb[8], nb[14], nb[20] = nb[1]^cur[2], nb[7]^cur[8], nb[13]^cur[14], nb[19]^cur[20]
	nb[3], nb[9], nb[15], nb[21] = nb[2]^cur[3], nb[8]^cur[9], nb[14]^cur[15], nb[20]^cur[21]
	nb[4], nb[10], nb[16], nb[22] = nb[3]^cur[4], nb[9]^cur[10], nb[15]^cur[16], nb[21]^cur[22]
	nb[5], nb[11], nb[17], nb[23] = nb[4]^cur[5], nb[10]^cur[11], nb[16]^cur[17], nb[22]^cur[23]
	keyexpanded = append(keyexpanded, nb)

	return nb
}

func keyExpansion256(cur Block, iteration int) Block {
	var nb = Block{ //nb = nextBlock
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	}
	rotword := []byte{cur[15], cur[23], cur[31], cur[7]}
	for i := 0; i < 4; i++ {
		upper, lower := splitBytes(rotword[i])
		rotword[i] = sbox[upper][lower]
	}
	rotword[0] = rotword[0] ^ cur[0] ^ rcon[iteration]
	rotword[1] = rotword[1] ^ cur[8]
	rotword[2] = rotword[2] ^ cur[16]
	rotword[3] = rotword[3] ^ cur[24]

	nb[0], nb[8], nb[16], nb[24] = rotword[0], rotword[1], rotword[2], rotword[3]
	nb[1], nb[9], nb[17], nb[25] = nb[0]^cur[1], nb[8]^cur[9], nb[16]^cur[17], nb[24]^cur[25]
	nb[2], nb[10], nb[18], nb[26] = nb[1]^cur[2], nb[9]^cur[10], nb[17]^cur[18], nb[25]^cur[26]
	nb[3], nb[11], nb[19], nb[27] = nb[2]^cur[3], nb[10]^cur[11], nb[18]^cur[19], nb[26]^cur[27]
	sw := []byte{nb[3], nb[11], nb[19], nb[27]} //sw = subword
	for i := 0; i < 4; i++ {
		upper, lower := splitBytes(sw[i])
		sw[i] = sbox[upper][lower]
	}
	nb[4], nb[12], nb[20], nb[28] = sw[0]^cur[4], sw[1]^cur[12], sw[2]^cur[20], sw[3]^cur[28]
	nb[5], nb[13], nb[21], nb[29] = nb[4]^cur[5], nb[12]^cur[13], nb[20]^cur[21], nb[28]^cur[29]
	nb[6], nb[14], nb[22], nb[30] = nb[5]^cur[6], nb[13]^cur[14], nb[21]^cur[22], nb[29]^cur[30]
	nb[7], nb[15], nb[23], nb[31] = nb[6]^cur[7], nb[14]^cur[15], nb[22]^cur[23], nb[30]^cur[31]
	keyexpanded = append(keyexpanded, nb)

	return nb
}

func splitBytes(b byte) (byte, byte) {
	return b >> 4, b & 0x0f
}

func subBytes(cur Block) Block {
	for i := 0; i < 16; i++ {
		upper, lower := splitBytes(cur[i])
		cur[i] = sbox[upper][lower]
	}
	return cur
}

func invSubBytes(cur Block) Block {
	for i := 0; i < 16; i++ {
		upper, lower := splitBytes(cur[i])
		cur[i] = invSbox[upper][lower]
	}
	return cur
}

func xtime(cur byte) []byte {
	var bytes []byte
	bytes = append(bytes, cur)
	for i := 1; i < 8; i++ { // first iteration done outside of for-loop
		if (cur >> 7) == 1 {
			cur = cur << 1
			cur = cur ^ 0x1b
		} else {
			cur = cur << 1
		}
		bytes = append(bytes, cur)
	}
	return bytes
}

func ffmult(cur []byte, multiplier byte) byte {
	if multiplier == 1 {
		return cur[0]
	} else if multiplier == 2 {
		return cur[1]
	} else if multiplier == 3 {
		return cur[0] ^ cur[1]
	} else if multiplier == 9 {
		return cur[0] ^ cur[3]
	} else if multiplier == 11 {
		return cur[0] ^ cur[1] ^ cur[3]
	} else if multiplier == 13 {
		return cur[0] ^ cur[2] ^ cur[3]
	} else if multiplier == 14 {
		return cur[1] ^ cur[2] ^ cur[3]
	}
	return 0
}

func mixColumnsAssist(cur []byte) []byte {
	a1 := ffmult(xtime(cur[0]), mm[0]) ^ ffmult(xtime(cur[1]), mm[1]) ^ ffmult(xtime(cur[2]), mm[2]) ^ ffmult(xtime(cur[3]), mm[3])
	a2 := ffmult(xtime(cur[0]), mm[4]) ^ ffmult(xtime(cur[1]), mm[5]) ^ ffmult(xtime(cur[2]), mm[6]) ^ ffmult(xtime(cur[3]), mm[7])
	a3 := ffmult(xtime(cur[0]), mm[8]) ^ ffmult(xtime(cur[1]), mm[9]) ^ ffmult(xtime(cur[2]), mm[10]) ^ ffmult(xtime(cur[3]), mm[11])
	a4 := ffmult(xtime(cur[0]), mm[12]) ^ ffmult(xtime(cur[1]), mm[13]) ^ ffmult(xtime(cur[2]), mm[14]) ^ ffmult(xtime(cur[3]), mm[15])
	return []byte{a1, a2, a3, a4}
}

func mixColumns(cur Block) Block {
	col1 := []byte{cur[0], cur[4], cur[8], cur[12]}
	col2 := []byte{cur[1], cur[5], cur[9], cur[13]}
	col3 := []byte{cur[2], cur[6], cur[10], cur[14]}
	col4 := []byte{cur[3], cur[7], cur[11], cur[15]}
	col1 = mixColumnsAssist(col1)
	col2 = mixColumnsAssist(col2)
	col3 = mixColumnsAssist(col3)
	col4 = mixColumnsAssist(col4)
	cur = Block{
		col1[0], col2[0], col3[0], col4[0],
		col1[1], col2[1], col3[1], col4[1],
		col1[2], col2[2], col3[2], col4[2],
		col1[3], col2[3], col3[3], col4[3],
	}
	return cur
}

func invMixColumns(cur Block) Block {
	col1 := []byte{cur[0], cur[4], cur[8], cur[12]}
	col2 := []byte{cur[1], cur[5], cur[9], cur[13]}
	col3 := []byte{cur[2], cur[6], cur[10], cur[14]}
	col4 := []byte{cur[3], cur[7], cur[11], cur[15]}
	col1 = invMixColumnsAssist(col1)
	col2 = invMixColumnsAssist(col2)
	col3 = invMixColumnsAssist(col3)
	col4 = invMixColumnsAssist(col4)
	cur = Block{
		col1[0], col2[0], col3[0], col4[0],
		col1[1], col2[1], col3[1], col4[1],
		col1[2], col2[2], col3[2], col4[2],
		col1[3], col2[3], col3[3], col4[3],
	}
	return cur
}

func invMixColumnsAssist(cur []byte) []byte {
	a1 := ffmult(xtime(cur[0]), iMM[0]) ^ ffmult(xtime(cur[1]), iMM[1]) ^ ffmult(xtime(cur[2]), iMM[2]) ^ ffmult(xtime(cur[3]), iMM[3])
	a2 := ffmult(xtime(cur[0]), iMM[4]) ^ ffmult(xtime(cur[1]), iMM[5]) ^ ffmult(xtime(cur[2]), iMM[6]) ^ ffmult(xtime(cur[3]), iMM[7])
	a3 := ffmult(xtime(cur[0]), iMM[8]) ^ ffmult(xtime(cur[1]), iMM[9]) ^ ffmult(xtime(cur[2]), iMM[10]) ^ ffmult(xtime(cur[3]), iMM[11])
	a4 := ffmult(xtime(cur[0]), iMM[12]) ^ ffmult(xtime(cur[1]), iMM[13]) ^ ffmult(xtime(cur[2]), iMM[14]) ^ ffmult(xtime(cur[3]), iMM[15])
	return []byte{a1, a2, a3, a4}
}

func shiftRows(cur Block) Block {
	cur[4], cur[5], cur[6], cur[7] = cur[5], cur[6], cur[7], cur[4]
	cur[8], cur[9], cur[10], cur[11] = cur[10], cur[11], cur[8], cur[9]
	cur[12], cur[13], cur[14], cur[15] = cur[15], cur[12], cur[13], cur[14]
	return cur
}

func invShiftRows(cur Block) Block {
	cur[4], cur[5], cur[6], cur[7] = cur[7], cur[4], cur[5], cur[6]
	cur[8], cur[9], cur[10], cur[11] = cur[10], cur[11], cur[8], cur[9]
	cur[12], cur[13], cur[14], cur[15] = cur[13], cur[14], cur[15], cur[12]
	return cur
}
