package org.bouncycastle.pqc.math.linearalgebra;
/**
* This class describes some operations with matrices over finite field <i>GF(2<sup>m</sup>)</i>
* with small <i>m</i> (1< m <32).
*
* @see Matrix
*/
public class GF2mMatrix
extends Matrix
{
/**
* finite field GF(2^m)
*/
protected GF2mField field;
/**
* For the matrix representation the array of type int[][] is used, thus
* every element of the array keeps one element of the matrix (element from
* finite field GF(2^m))
*/
protected int[][] matrix;
/**
* Constructor.
*
* @param field a finite field GF(2^m)
* @param enc byte[] matrix in byte array form
*/
public GF2mMatrix(GF2mField field, byte[] enc)
{
this.field = field;
// decode matrix
int d = 8;
int count = 1;
while (field.getDegree() > d)
{
count++;
d += 8;
}
if (enc.length < 5)
{
throw new IllegalArgumentException(
" Error: given array is not encoded matrix over GF(2^m)");
}
this.numRows = ((enc[3] & 0xff) << 24) ^ ((enc[2] & 0xff) << 16)
^ ((enc[1] & 0xff) << 8) ^ (enc[0] & 0xff);
int n = count * this.numRows;
if ((this.numRows <= 0) || (((enc.length - 4) % n) != 0))
{
throw new IllegalArgumentException(
" Error: given array is not encoded matrix over GF(2^m)");
}
this.numColumns = (enc.length - 4) / n;
matrix = new int[this.numRows][this.numColumns];
count = 4;
for (int i = 0; i < this.numRows; i++)
{
for (int j = 0; j < this.numColumns; j++)
{
for (int jj = 0; jj < d; jj += 8)
{
matrix[i][j] ^= (enc[count++] & 0x000000ff) << jj;
}
if (!this.field.isElementOfThisField(matrix[i][j]))
{
throw new IllegalArgumentException(
" Error: given array is not encoded matrix over GF(2^m)");
}
}
}
}
/**
* Copy constructor.
*
* @param other another {@link GF2mMatrix}
*/
public GF2mMatrix(GF2mMatrix other)
{
numRows = other.numRows;
numColumns = other.numColumns;
field = other.field;
matrix = new int[numRows][];
for (int i = 0; i < numRows; i++)
{
matrix[i] = IntUtils.clone(other.matrix[i]);
}
}
/**
* Constructor.
*
* @param field a finite field GF(2^m)
* @param matrix the matrix as int array. Only the reference is copied.
*/
protected GF2mMatrix(GF2mField field, int[][] matrix)
{
this.field = field;
this.matrix = matrix;
numRows = matrix.length;
numColumns = matrix[0].length;
}
/**
* @return a byte array encoding of this matrix
*/
public byte[] getEncoded()
{
int d = 8;
int count = 1;
while (field.getDegree() > d)
{
count++;
d += 8;
}
byte[] bf = new byte[this.numRows * this.numColumns * count + 4];
bf[0] = (byte)(this.numRows & 0xff);
bf[1] = (byte)((this.numRows >>> 8) & 0xff);
bf[2] = (byte)((this.numRows >>> 16) & 0xff);
bf[3] = (byte)((this.numRows >>> 24) & 0xff);
count = 4;
for (int i = 0; i < this.numRows; i++)
{
for (int j = 0; j < this.numColumns; j++)
{
for (int jj = 0; jj < d; jj += 8)
{
bf[count++] = (byte)(matrix[i][j] >>> jj);
}
}
}
return bf;
}
/**
* Check if this is the zero matrix (i.e., all entries are zero).
*
* @return <tt>true</tt> if this is the zero matrix
*/
public boolean isZero()
{
for (int i = 0; i < numRows; i++)
{
for (int j = 0; j < numColumns; j++)
{
if (matrix[i][j] != 0)
{
return false;
}
}
}
return true;
}
/**
* Compute the inverse of this matrix.
*
* @return the inverse of this matrix (newly created).
*/
public Matrix computeInverse()
{
if (numRows != numColumns)
{
throw new ArithmeticException("Matrix is not invertible.");
}
// clone this matrix
int[][] tmpMatrix = new int[numRows][numRows];
for (int i = numRows - 1; i >= 0; i--)
{
tmpMatrix[i] = IntUtils.clone(matrix[i]);
}
// initialize inverse matrix as unit matrix
int[][] invMatrix = new int[numRows][numRows];
for (int i = numRows - 1; i >= 0; i--)
{
invMatrix[i][i] = 1;
}
// simultaneously compute Gaussian reduction of tmpMatrix and unit
// matrix
for (int i = 0; i < numRows; i++)
{
// if diagonal element is zero
if (tmpMatrix[i][i] == 0)
{
boolean foundNonZero = false;
// find a non-zero element in the same column
for (int j = i + 1; j < numRows; j++)
{
if (tmpMatrix[j][i] != 0)
{
// found it, swap rows ...
foundNonZero = true;
swapColumns(tmpMatrix, i, j);
swapColumns(invMatrix, i, j);
// ... and quit searching
j = numRows;
continue;
}
}
// if no non-zero element was found
if (!foundNonZero)
{
// the matrix is not invertible
throw new ArithmeticException("Matrix is not invertible.");
}
}
// normalize i-th row
int coef = tmpMatrix[i][i];
int invCoef = field.inverse(coef);
multRowWithElementThis(tmpMatrix[i], invCoef);
multRowWithElementThis(invMatrix[i], invCoef);
// normalize all other rows
for (int j = 0; j < numRows; j++)
{
if (j != i)
{
coef = tmpMatrix[j][i];
if (coef != 0)
{
int[] tmpRow = multRowWithElement(tmpMatrix[i], coef);
int[] tmpInvRow = multRowWithElement(invMatrix[i], coef);
addToRow(tmpRow, tmpMatrix[j]);
addToRow(tmpInvRow, invMatrix[j]);
}
}
}
}
return new GF2mMatrix(field, invMatrix);
}
private static void swapColumns(int[][] matrix, int first, int second)
{
int[] tmp = matrix[first];
matrix[first] = matrix[second];
matrix[second] = tmp;
}
private void multRowWithElementThis(int[] row, int element)
{
for (int i = row.length - 1; i >= 0; i--)
{
row[i] = field.mult(row[i], element);
}
}
private int[] multRowWithElement(int[] row, int element)
{
int[] result = new int[row.length];
for (int i = row.length - 1; i >= 0; i--)
{
result[i] = field.mult(row[i], element);
}
return result;
}
/**
* Add one row to another.
*
* @param fromRow the addend
* @param toRow the row to add to
*/
private void addToRow(int[] fromRow, int[] toRow)
{
for (int i = toRow.length - 1; i >= 0; i--)
{
toRow[i] = field.add(fromRow[i], toRow[i]);
}
}
public Matrix rightMultiply(Matrix a)
{
throw new RuntimeException("Not implemented.");
}
public Matrix rightMultiply(Permutation perm)
{
throw new RuntimeException("Not implemented.");
}
public Vector leftMultiply(Vector vector)
{
throw new RuntimeException("Not implemented.");
}
public Vector rightMultiply(Vector vector)
{
throw new RuntimeException("Not implemented.");
}
/**
* Checks if given object is equal to this matrix. The method returns false
* whenever the given object is not a matrix over GF(2^m).
*
* @param other object
* @return true or false
*/
public boolean equals(Object other)
{
if (other == null || !(other instanceof GF2mMatrix))
{
return false;
}
GF2mMatrix otherMatrix = (GF2mMatrix)other;
if ((!this.field.equals(otherMatrix.field))
|| (otherMatrix.numRows != this.numColumns)
|| (otherMatrix.numColumns != this.numColumns))
{
return false;
}
for (int i = 0; i < this.numRows; i++)
{
for (int j = 0; j < this.numColumns; j++)
{
if (this.matrix[i][j] != otherMatrix.matrix[i][j])
{
return false;
}
}
}
return true;
}
public int hashCode()
{
int hash = (this.field.hashCode() * 31 + numRows) * 31 + numColumns;
for (int i = 0; i < this.numRows; i++)
{
for (int j = 0; j < this.numColumns; j++)
{
hash = hash * 31 + matrix[i][j];
}
}
return hash;
}
public String toString()
{
String str = this.numRows + " x " + this.numColumns + " Matrix over "
+ this.field.toString() + ": \n";
for (int i = 0; i < this.numRows; i++)
{
for (int j = 0; j < this.numColumns; j++)
{
str = str + this.field.elementToStr(matrix[i][j]) + " : ";
}
str = str + "\n";
}
return str;
}
}