package org.bouncycastle.crypto.modes.kgcm;

import org.bouncycastle.math.raw.Interleave;

/**
 * Utilities for the GF(2^m) field with corresponding extension polynomial:
 * <pre>
 * GF (2^128) -&gt; x^128 + x^7 + x^2 + x + 1
 * </pre>
 * The representation is little-endian arrays of 64-bit words
*/
public class KGCMUtil_128
{
    public static final int SIZE = 2;

    public static void add(long[] x, long[] y, long[] z)
    {
        z[0] = x[0] ^ y[0];
        z[1] = x[1] ^ y[1];
    }

    public static void copy(long[] x, long[] z)
    {
        z[0] = x[0];
        z[1] = x[1];
    }

    public static boolean equal(long[] x, long[] y)
    {
        long d = 0L;
        d |= x[0] ^ y[0];
        d |= x[1] ^ y[1];
        return d == 0L;
    }

    public static void multiply(long[] x, long[] y, long[] z)
    {
        long x0 = x[0], x1 = x[1];
        long y0 = y[0], y1 = y[1];
        long z0 = 0, z1 = 0, z2 = 0;

        for (int j = 0; j < 64; ++j)
        {
            long m0 = -(x0 & 1L); x0 >>>= 1;
            z0 ^= (y0 & m0);
            z1 ^= (y1 & m0);

            long m1 = -(x1 & 1L); x1 >>>= 1;
            z1 ^= (y0 & m1);
            z2 ^= (y1 & m1);

            long c = y1 >> 63;
            y1 = (y1 << 1) | (y0 >>> 63);
            y0 = (y0 << 1) ^ (c & 0x87L);
        }

        z0 ^= z2 ^ (z2 <<   1) ^ (z2 <<   2) ^ (z2 <<   7);
        z1 ^=      (z2 >>> 63) ^ (z2 >>> 62) ^ (z2 >>> 57);      

        z[0] = z0; z[1] = z1;
    }

    public static void multiplyX(long[] x, long[] z)
    {
        long x0 = x[0], x1 = x[1];
        long m = x1 >> 63;
        z[0] = (x0 << 1) ^ (m & 0x87L);
        z[1] = (x1 << 1) | (x0 >>> 63);
    }

    public static void multiplyX8(long[] x, long[] z)
    {
        long x0 = x[0], x1 = x[1];
        long c = x1 >>> 56;
        z[0] = (x0 << 8) ^ c ^ (c << 1) ^ (c << 2) ^ (c << 7);
        z[1] = (x1 << 8) | (x0 >>> 56);
    }

    public static void one(long[] z)
    {
        z[0] = 1;
        z[1] = 0;
    }

    public static void square(long[] x, long[] z)
    {
        long[] t  = new long[4];
        Interleave.expand64To128(x[0], t, 0);
        Interleave.expand64To128(x[1], t, 2);

        long z0 = t[0], z1 = t[1], z2 = t[2], z3 = t[3];

        z1 ^= z3 ^ (z3 <<   1) ^ (z3 <<   2) ^ (z3 <<   7);
        z2 ^=      (z3 >>> 63) ^ (z3 >>> 62) ^ (z3 >>> 57);      

        z0 ^= z2 ^ (z2 <<   1) ^ (z2 <<   2) ^ (z2 <<   7);
        z1 ^=      (z2 >>> 63) ^ (z2 >>> 62) ^ (z2 >>> 57);      

        z[0] = z0;
        z[1] = z1;
    }

    public static void x(long[] z)
    {
        z[0] = 2;
        z[1] = 0;
    }

    public static void zero(long[] z)
    {
        z[0] = 0;
        z[1] = 0;
    }
}
