6

By definition, branch number

Definition: The branch number of a linear transformation $F$ is $$min_{a\neq0}(W(a) + W(F(a)))$$

Source here (7.3.1)

For AES MixColumns $a \in GF(2^8)^4$ since the input is the four bytes in a column of the state.

Where $W(a)$ is a weight vector i.e. the number of nonzero components of the vector

$$a \in GF(2^m)^4, a = (a_1, \ldots, a_4)\\ W(a) \iff ||a|| = |\{i | a_i\neq 0 \}|, i = 1,\ldots,4$$

In AES uses a pre-defined matrix operations MixColumns. I need prove (like the proof of a theorem) that the branch number of matrix is equal to 5.

Questions:

  1. In the same article (7.3.1) is said

    the output can have at most 4 active bytes

    and

    Hence, the upper bound for the branch number is 5

    In such a way that $W(F(a))$ the maximum can be $4$ (why?) and $W(a) = 1$. Why $W(a) = 1$, because the number of non-zero component can be greater than $1$? Or it is there to pay attention to $min()$?

  2. How to calculate $W(F(a))$, for each $a \in GF(2^m)$ ?

The final answer:

Use theorem No. 2 (on page 4) here

is MDS if and only if every square submatrix of A is nonsingular

Ok, the initial data taken from here.

To test, I wrote a script test.py (see below) that:

  1. Defines all sub-matrices of the original
  2. For each of the sub-matrices calculate the determinant in GF(2^8)
  3. The result is an array of values of the determinant of each submatrix.

test.py

from functools import reduce
import sys


# ===========================Galois field===========================

# constants used in the multGF2 function
mask1 = mask2 = polyred = None

def setGF2(degree, irPoly):
    # Define parameters of binary finite field GF(2^m)/g(x)
    #    - degree: extension degree of binary field
    #    - irPoly: coefficients of irreducible polynomial g(x)

    def i2P(sInt):
        # Convert integer into a polynomial
        return [(sInt >> i) & 1
                for i in reversed(range(sInt.bit_length()))]    

    global mask1, mask2, polyred
    mask1 = mask2 = 1 << degree
    mask2 -= 1
    polyred = reduce(lambda x, y: (x << 1) + y, i2P(irPoly)[1:])

def multGF2(p1, p2):
    # Multiply two polynomials in GF(2^m)/g(x)
    p = 0
    while p2:
        if p2 & 1:
            p ^= p1
        p1 <<= 1
        if p1 & mask1:
            p1 ^= polyred
        p2 >>= 1
    return p & mask2

def determinant(matrix, mul):
  width = len(matrix)
  if width == 1:
      return multGF2(mul, matrix[0][0])
  else:
      sign = 1
      total = 0
      for i in range(width):
          m = []
          for j in range(1, width):
              buff = []
              for k in range(width):
                  if k != i:
                      buff.append(matrix[j][k])
              m.append(buff)
          total = total ^ (multGF2(mul, determinant(m, multGF2(sign, matrix[0][i]))))
      return total

# ===========================All submatrix===========================
import numpy as np

def get_all_sub_mat(mat):
    rows = len(mat)
    cols = len(mat[0])
    def ContinSubSeq(lst):
        size=len(lst)
        for start in range(size):
            for end in range(start+1,size+1):
                yield (start,end)
    for start_row,end_row in ContinSubSeq(list(range(rows))):
        for start_col,end_col in ContinSubSeq(list(range(cols))):
            yield [i[start_col:end_col] for i in mat[start_row:end_row] ]

def swap_cols(arr, frm, to):
  arr = np.matrix(arr)
  arr[:,[frm, to]] = arr[:,[to, frm]]
  return arr.tolist()

def swap_rows(arr, frm, to):
  arr = np.matrix(arr)
  arr[[frm, to],:] = arr[[to, frm],:]
  return arr.tolist()

def print_matrix(matrix):
  matrix = np.matrix(matrix)
  print matrix

submatrix = []
def foo(matrix):
  for i in get_all_sub_mat(matrix):
    if len(i) == len(i[0]) and len(i) != 1:
      submatrix.append(i)

# Initial matrix
matrix = [
  [2, 3, 1, 1],
  [1, 2, 3, 1],
  [1, 1, 2, 3],
  [3, 1, 1, 2]
]

# All submatrix here
for i in [[0,0], [1,2], [1,3], [2,3]]:
  _matrix = swap_cols(matrix, i[0], i[1])
  for j in [[0,0], [1,2], [1,3], [2,3]]:
    _matrix = swap_rows(matrix, i[0], i[1])
    foo(_matrix)

# print len(submatrix)  # 224

if __name__ == "__main__":
    setGF2(8, 0b10001)  # 0b10001 equal modulo x^4+1 from https://en.wikipedia.org/wiki/Rijndael_mix_columns
    data = []
    N = 2 ** 8

    result = []
    for m in submatrix:
      result.append(determinant(m, 1))
    print 'Final result: ', result
    print '0 in result: ', 0 in result

Thanks @kodlu for the help!

kelalaka
  • 48,443
  • 11
  • 116
  • 196
Sasha
  • 63
  • 1
  • 4

1 Answers1

8

The AES MixColumns operator ensures that the 8 bytes (4 in the input column 4 in the output column) form the codewords of an MDS code over $GF(2^8)$, which means the minimum weight of the code, which is 5, equals the number of nonzero bytes.

Any nonzer byte contributes 1 to the minimum weight, by definition of Hamming Weight over $GF(2^8)$. A nonzero symbol has weight 1, regardless of how many bits of the eight is nonzero.

See the answer to this question for more.

Edit: The Singleton bound states that minimum distance is at least $n-k+1.$ Here $n=8, k=4.$ Such a code is MDS and proving MDS depends on the code structure. Look up MDS codes and Reed Solomon codes.

More concretely, a linear code is the nullspace of its parity check matrix. So if that matrix has all its collections of $d-1$ columns linearly independent (over $GF(2^8)$ here) then its minimum weight codeword must be $d$ or more. Moreover a code is MDS if and only if its dual is MDS so we can just consider the generator matrix and observe all collections of 4columns of $[A| I]$ are indeed linearly independent.So, $d\geq 5.$ But by singleton bound $d\leq 5.$ QED.

See the following link for more details:

kodlu
  • 22,423
  • 2
  • 27
  • 57
  • in AES uses a fixed polynomial $$c(x) = 3x^3 + x^2 + x + 2$$

    The coefficients have been chosen in such a way that the upper bound is reached.
    "Any nonzer byte contributes 1 to the minimum weight" - Thank you, well done!

    "ensures that the 8 bytes" and "See the answer to this question for more" - Yeah, I saw this answer, but still not very well understood, how is the calculation (from a mathematical point of view)

    – Sasha Jan 04 '17 at 19:36
  • in other words, how to prove "The branch number, which is the minimum weight of the corresponding linear code is 4, in $GF(2^n)$ for all $n$"? – Sasha Jan 04 '17 at 20:00
  • 2
    The branch number is 5 not 4. Also, your definition of the weight uses the bit weight not byte weight., which is wrong. – kodlu Jan 04 '17 at 20:21
  • Do you mind me editing the question since the way the weight is used from the NIST doc is the problem. – kodlu Jan 04 '17 at 20:26
  • Ok, I use byte weight. But I still don't understand why 5? In the documentation of AES is described as a fact. There is no mathematical explanation of why chosen this polynomial (exactly this polynomial allows to obtain a branch number = 5) – Sasha Jan 04 '17 at 20:28
  • Yes, you can edit :) – Sasha Jan 04 '17 at 20:31
  • Ok, I read about MDS codes and Reed Solomon codes, but could you explain: if we define the branch number through $$n-k+1$$ what is the need for a predetermined MDS matrix this? And what does it mean "Hence, the upper bound for the branch number is 5" from here - that is, the result may be less than 5 or what? I'm a little confused :) – Sasha Jan 04 '17 at 21:57
  • You need some MDS Matrix. A general MixColumns matrix may have minimum weight (equal to branch number) as low as 2. Since it needs to be invertible in an SPN structure, it can't map a nonzero vector to a zero vector, hence the two (one input and one output byte must be nonzero). The MDS matrix chosen in AES has low weight coefficients and is circulant, giving fast implementation. – kodlu Jan 04 '17 at 22:34
  • Yes, we need some MDS matrix BUT the matrix used in AES is pre-determined. And this pre-determined matrix allows to get the branch number = 5. In other words, the authors of AES chose this matrix for a reason (How did they do it? Not at random the same). This is the question, is it possible to mathematically prove that this matrix allows to obtain branch number = 5? There is a way - brute force, but I want to prove analytically – Sasha Jan 05 '17 at 08:39
  • The full weight distribution of MDS codes is known, so they are all equivalent in terms of diffusion. See this other question and answer: https://crypto.stackexchange.com/questions/35823/how-to-calculate-active-s-boxes-from-branch-number – kodlu Jul 06 '23 at 13:24