// 
// Decompiled by Procyon v0.6.0
// 

package com.google.crypto.tink.aead.internal;

import java.security.GeneralSecurityException;
import com.google.crypto.tink.subtle.Bytes;
import java.util.Arrays;

public class Poly1305
{
    public static final int MAC_TAG_SIZE_IN_BYTES = 16;
    public static final int MAC_KEY_SIZE_IN_BYTES = 32;
    
    private Poly1305() {
    }
    
    private static long load32(final byte[] in, final int idx) {
        return (long)((in[idx] & 0xFF) | (in[idx + 1] & 0xFF) << 8 | (in[idx + 2] & 0xFF) << 16 | (in[idx + 3] & 0xFF) << 24) & 0xFFFFFFFFL;
    }
    
    private static long load26(final byte[] in, final int idx, final int shift) {
        return load32(in, idx) >> shift & 0x3FFFFFFL;
    }
    
    private static void toByteArray(final byte[] output, long num, final int idx) {
        for (int i = 0; i < 4; ++i, num >>= 8) {
            output[idx + i] = (byte)(num & 0xFFL);
        }
    }
    
    private static void copyBlockSize(final byte[] output, final byte[] in, final int idx) {
        final int copyCount = Math.min(16, in.length - idx);
        System.arraycopy(in, idx, output, 0, copyCount);
        output[copyCount] = 1;
        if (copyCount != 16) {
            Arrays.fill(output, copyCount + 1, output.length, (byte)0);
        }
    }
    
    public static byte[] computeMac(final byte[] key, final byte[] data) {
        if (key.length != 32) {
            throw new IllegalArgumentException("The key length in bytes must be 32.");
        }
        long h0 = 0L;
        long h2 = 0L;
        long h3 = 0L;
        long h4 = 0L;
        long h5 = 0L;
        final long r0 = load26(key, 0, 0) & 0x3FFFFFFL;
        final long r2 = load26(key, 3, 2) & 0x3FFFF03L;
        final long r3 = load26(key, 6, 4) & 0x3FFC0FFL;
        final long r4 = load26(key, 9, 6) & 0x3F03FFFL;
        final long r5 = load26(key, 12, 8) & 0xFFFFFL;
        final long s1 = r2 * 5L;
        final long s2 = r3 * 5L;
        final long s3 = r4 * 5L;
        final long s4 = r5 * 5L;
        final byte[] buf = new byte[17];
        for (int i = 0; i < data.length; i += 16) {
            copyBlockSize(buf, data, i);
            h0 += load26(buf, 0, 0);
            h2 += load26(buf, 3, 2);
            h3 += load26(buf, 6, 4);
            h4 += load26(buf, 9, 6);
            h5 += (load26(buf, 12, 8) | (long)(buf[16] << 24));
            final long d0 = h0 * r0 + h2 * s4 + h3 * s3 + h4 * s2 + h5 * s1;
            long d2 = h0 * r2 + h2 * r0 + h3 * s4 + h4 * s3 + h5 * s2;
            long d3 = h0 * r3 + h2 * r2 + h3 * r0 + h4 * s4 + h5 * s3;
            long d4 = h0 * r4 + h2 * r3 + h3 * r2 + h4 * r0 + h5 * s4;
            long d5 = h0 * r5 + h2 * r4 + h3 * r3 + h4 * r2 + h5 * r0;
            long c = d0 >> 26;
            h0 = (d0 & 0x3FFFFFFL);
            d2 += c;
            c = d2 >> 26;
            h2 = (d2 & 0x3FFFFFFL);
            d3 += c;
            c = d3 >> 26;
            h3 = (d3 & 0x3FFFFFFL);
            d4 += c;
            c = d4 >> 26;
            h4 = (d4 & 0x3FFFFFFL);
            d5 += c;
            c = d5 >> 26;
            h5 = (d5 & 0x3FFFFFFL);
            h0 += c * 5L;
            c = h0 >> 26;
            h0 &= 0x3FFFFFFL;
            h2 += c;
        }
        long c = h2 >> 26;
        h2 &= 0x3FFFFFFL;
        h3 += c;
        c = h3 >> 26;
        h3 &= 0x3FFFFFFL;
        h4 += c;
        c = h4 >> 26;
        h4 &= 0x3FFFFFFL;
        h5 += c;
        c = h5 >> 26;
        h5 &= 0x3FFFFFFL;
        h0 += c * 5L;
        c = h0 >> 26;
        h0 &= 0x3FFFFFFL;
        h2 += c;
        long g0 = h0 + 5L;
        c = g0 >> 26;
        g0 &= 0x3FFFFFFL;
        long g2 = h2 + c;
        c = g2 >> 26;
        g2 &= 0x3FFFFFFL;
        long g3 = h3 + c;
        c = g3 >> 26;
        g3 &= 0x3FFFFFFL;
        long g4 = h4 + c;
        c = g4 >> 26;
        g4 &= 0x3FFFFFFL;
        final long g5 = h5 + c - 67108864L;
        long mask = g5 >> 63;
        h0 &= mask;
        h2 &= mask;
        h3 &= mask;
        h4 &= mask;
        h5 &= mask;
        mask ^= -1L;
        h0 |= (g0 & mask);
        h2 |= (g2 & mask);
        h3 |= (g3 & mask);
        h4 |= (g4 & mask);
        h5 |= (g5 & mask);
        h0 = ((h0 | h2 << 26) & 0xFFFFFFFFL);
        h2 = ((h2 >> 6 | h3 << 20) & 0xFFFFFFFFL);
        h3 = ((h3 >> 12 | h4 << 14) & 0xFFFFFFFFL);
        h4 = ((h4 >> 18 | h5 << 8) & 0xFFFFFFFFL);
        c = h0 + load32(key, 16);
        h0 = (c & 0xFFFFFFFFL);
        c = h2 + load32(key, 20) + (c >> 32);
        h2 = (c & 0xFFFFFFFFL);
        c = h3 + load32(key, 24) + (c >> 32);
        h3 = (c & 0xFFFFFFFFL);
        c = h4 + load32(key, 28) + (c >> 32);
        h4 = (c & 0xFFFFFFFFL);
        final byte[] mac = new byte[16];
        toByteArray(mac, h0, 0);
        toByteArray(mac, h2, 4);
        toByteArray(mac, h3, 8);
        toByteArray(mac, h4, 12);
        return mac;
    }
    
    public static void verifyMac(final byte[] key, final byte[] data, final byte[] mac) throws GeneralSecurityException {
        if (!Bytes.equal(computeMac(key, data), mac)) {
            throw new GeneralSecurityException("invalid MAC");
        }
    }
}
