// stripScalars gets rid of unknown scalars in each block of the affine layer. It leaves it exactly equal to MixColumns, // but there is an unknown scalar in each block that will move into the S-box layers. func (al *affineLayer) stripScalars() (encoding.ConcatenatedBlock, encoding.ConcatenatedBlock) { input, output := [16]encoding.ByteLinear{}, [16]encoding.ByteLinear{} for pos := 0; pos < 16; pos += 4 { found := false for guess := 1; guess < 256 && !found; guess++ { // Take a guess for the input scalar on the first column. input[pos], _ = encoding.DecomposeByteLinear(encoding.NewByteMultiplication(number.ByteFieldElem(guess))) // Given input scalar on first column, calculate output scalars on all rows. for i := pos; i < pos+4; i++ { mc, _ := mixColumns[i%4][0].Invert() output[i] = encoding.NewByteLinear( al.getBlock(i, pos).Compose(input[pos].Backwards).Compose(mc), ) } // Given output scalar on each row, calculate input scalars on all columns. for i := pos + 1; i < pos+4; i++ { mc, _ := mixColumns[0][i%4].Invert() input[i] = encoding.NewByteLinear( al.getBlock(pos, i).Compose(output[pos].Backwards).Compose(mc), ) } // Verify that guess is consistent. found = true for i := pos; i < pos+4 && found; i++ { for j := pos; j < pos+4 && found; j++ { cand := al.getBlock(i, j).Compose(output[i].Backwards).Compose(input[j].Backwards) real := mixColumns[i%4][j%4] if !cand.Equals(real) { found = false } } } } if !found { panic("Failed to disambiguate block affine layer!") } } in, out := encoding.ConcatenatedBlock{}, encoding.ConcatenatedBlock{} for pos := 0; pos < 16; pos++ { in[pos], out[pos] = input[pos], output[pos] } return in, out }
// blockMaskEncoding concatenates all the mask encodings for InputMask or TBoxOutputMask into a block encoding, so that // it can easily be put on the output of one of the Block tables. // // position is the index of the Block table and shift is the permutation that will be applied between this round and the // next or noshift if this is an input encoding; the other parameters are explained in MaskEncoding documentation. func blockMaskEncoding(rs *random.Source, position int, surface common.Surface, shift func(int) int) encoding.Block { out := encoding.ConcatenatedBlock{} for i := 0; i < 16; i++ { out[i] = encoding.ConcatenatedByte{ maskEncoding(rs, surface)(position, 2*i+0), maskEncoding(rs, surface)(position, 2*i+1), } if surface == common.Inside { out[i] = encoding.ComposedBytes{ encoding.NewByteLinear(common.MixingBijection(rs, 8, -1, shift(i))), out[i], } } } return out }
// blockParasites returns the input parasite of the block at the given row and column of the linear part of al, or nil // if the block is zero. func (al *affineLayer) blockParasite(row, col int) (input *parasite) { block := al.getBlock(row, col) if toString(block) == "0000000000000000" { return nil } enc := encoding.NewByteLinear(block) for _, input = range parasites { cand, _ := encoding.DecomposeByteLinear(encoding.ComposedBytes{ encoding.InverseByte{subBytes}, encoding.InverseByte{input}, enc, }) _, ok := parasites[toString(cand.Forwards)] if ok { return } } panic("unable to determine input parasite") }
// inputParasite returns the non-GF(2^8) part of the parasite on the output at position col. func (al *affineLayer) inputParasite(col int) encoding.Byte { block := col / 4 row := 4 * (col / 4) row0, col0 := row%4, col%4 row1, col1 := (row0+1)%4, (col0+1)%4 rowT, colT := row1+(4*block), col1+(4*block) blockAA, blockAB := al.getBlock(row, col), al.getBlock(row, colT) blockBA, blockBB := al.getBlock(rowT, col), al.getBlock(rowT, colT) blockAA, _ = blockAA.Invert() blockBB, _ = blockBB.Invert() B := blockAA.Compose(blockAB).Compose(blockBB).Compose(blockBA) lambda := unMixColumns[row0][col0].Compose(mixColumns[row0][col1]). Compose(unMixColumns[row1][col1]).Compose(mixColumns[row1][col0]) return encoding.NewByteLinear(findMatrix( B.LeftStretch().Add(lambda.RightStretch()).NullSpace(), )) }
func generateKeys(rs *random.Source, opts common.KeyGenerationOpts, out *Construction, inputMask, outputMask *matrix.Matrix, shift func(int) int, skinny func(int) table.Byte, wide func(int, int) table.Word) { // Generate input and output encodings. common.GenerateMasks(rs, opts, inputMask, outputMask) // Generate the Input Mask slices and XOR tables. for pos := 0; pos < 16; pos++ { out.InputMask[pos] = encoding.BlockTable{ encoding.IdentityByte{}, blockMaskEncoding(rs, pos, common.Inside, shift), common.BlockMatrix{Linear: *inputMask, Position: pos}, } } out.InputXORTables = common.BlockNibbleXORTables( maskEncoding(rs, common.Inside), xorEncoding(rs, 10, common.Inside), roundEncoding(rs, -1, common.Outside, shift), ) // Generate round material. for round := 0; round < 9; round++ { for pos := 0; pos < 16; pos++ { // Generate a word-sized mixing bijection and stick it on the end of the T-Box/Tyi Table. mb := common.MixingBijection(rs, 32, round, pos/4) // Build the T-Box and Tyi Table for this round and position in the state matrix. out.TBoxTyiTable[round][pos] = encoding.WordTable{ encoding.ComposedBytes{ encoding.NewByteLinear(common.MixingBijection(rs, 8, round-1, pos)), byteRoundEncoding(rs, round-1, pos, common.Outside, common.NoShift), }, encoding.ComposedWords{ encoding.ConcatenatedWord{ encoding.NewByteLinear(common.MixingBijection(rs, 8, round, shift(pos/4*4+0))), encoding.NewByteLinear(common.MixingBijection(rs, 8, round, shift(pos/4*4+1))), encoding.NewByteLinear(common.MixingBijection(rs, 8, round, shift(pos/4*4+2))), encoding.NewByteLinear(common.MixingBijection(rs, 8, round, shift(pos/4*4+3))), }, encoding.NewWordLinear(mb), wordStepEncoding(rs, round, pos, common.Inside), }, wide(round, pos), } // Encode the inverse of the mixing bijection from above in the MB^(-1) table for this round and position. mbInv, _ := mb.Invert() out.MBInverseTable[round][pos] = encoding.WordTable{ byteRoundEncoding(rs, round, pos, common.Inside, common.NoShift), wordStepEncoding(rs, round, pos, common.Outside), mbInverseTable{mbInv, uint(pos) % 4}, } } } // Generate the High and Low XOR Tables for reach round. out.HighXORTable = xorTables(rs, common.Inside, common.NoShift) out.LowXORTable = xorTables(rs, common.Outside, shift) // Generate the 10th T-Box/Output Mask slices and XOR tables. for pos := 0; pos < 16; pos++ { out.TBoxOutputMask[pos] = encoding.BlockTable{ encoding.ComposedBytes{ encoding.NewByteLinear(common.MixingBijection(rs, 8, 8, pos)), byteRoundEncoding(rs, 8, pos, common.Outside, common.NoShift), }, blockMaskEncoding(rs, pos, common.Outside, shift), table.ComposedToBlock{ Heads: skinny(pos), Tails: common.BlockMatrix{Linear: *outputMask, Position: pos}, }, } } out.OutputXORTables = common.BlockNibbleXORTables( maskEncoding(rs, common.Outside), xorEncoding(rs, 10, common.Outside), func(position int) encoding.Nibble { return encoding.IdentityByte{} }, ) }
package toy import ( "github.com/OpenWhiteBox/primitives/encoding" "github.com/OpenWhiteBox/primitives/gfmatrix" "github.com/OpenWhiteBox/primitives/matrix" "github.com/OpenWhiteBox/primitives/number" ) var ( // subBytes is the linear part of of AES's SubBytes transformation. subBytes = encoding.NewByteLinear(matrix.Matrix{ matrix.Row{0xF1}, // 0b11110001 matrix.Row{0xE3}, // 0b11100011 matrix.Row{0xC7}, // 0b11000111 matrix.Row{0x8F}, // 0b10001111 matrix.Row{0x1F}, // 0b00011111 matrix.Row{0x3E}, // 0b00111110 matrix.Row{0x7C}, // 0b01111100 matrix.Row{0xF8}, // 0b11111000 }) // blocks maps an 8-by-8 matrix to its MixColumns coefficient, c. The matrix is $[c] \circ subBytes$ (from above). // This is used for compressing affine layers to matrices over F_{2^8}. blocks = map[string]number.ByteFieldElem{ "0000000000000000": 0, "f1e3c78f1f3e7cf8": 1, "f809e33f771f3e7c": 2, "09ea24b068214284": 3, } // smallRound is a compressed round matrix, without ShiftRows (so essentially just MixColumns four times). This is