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; } }