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

package org.bouncycastle.crypto.engines;

import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.DataLengthException;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.util.Bytes;
import java.util.Arrays;

public class ElephantEngine extends AEADBaseEngine
{
    private byte[] npub;
    private byte[] expanded_key;
    private int nb_its;
    private byte[] ad;
    private int adOff;
    private int adlen;
    private final byte[] tag_buffer;
    private byte[] previous_mask;
    private byte[] current_mask;
    private byte[] next_mask;
    private final byte[] buffer;
    private final byte[] previous_outputMessage;
    private final Permutation instance;
    
    public ElephantEngine(final ElephantParameters elephantParameters) {
        this.KEY_SIZE = 16;
        this.IV_SIZE = 12;
        switch (elephantParameters.ordinal()) {
            case 0: {
                this.BlockSize = 20;
                this.instance = new Dumbo();
                this.MAC_SIZE = 8;
                this.algorithmName = "Elephant 160 AEAD";
                break;
            }
            case 1: {
                this.BlockSize = 22;
                this.instance = new Jumbo();
                this.algorithmName = "Elephant 176 AEAD";
                this.MAC_SIZE = 8;
                break;
            }
            case 2: {
                this.BlockSize = 25;
                this.instance = new Delirium();
                this.algorithmName = "Elephant 200 AEAD";
                this.MAC_SIZE = 16;
                break;
            }
            default: {
                throw new IllegalArgumentException("Invalid parameter settings for Elephant");
            }
        }
        this.tag_buffer = new byte[this.BlockSize];
        this.previous_mask = new byte[this.BlockSize];
        this.current_mask = new byte[this.BlockSize];
        this.next_mask = new byte[this.BlockSize];
        this.buffer = new byte[this.BlockSize];
        this.previous_outputMessage = new byte[this.BlockSize];
        this.setInnerMembers(ProcessingBufferType.Immediate, AADOperatorType.Stream, DataOperatorType.Counter);
    }
    
    private byte rotl(final byte b) {
        return (byte)(b << 1 | (b & 0xFF) >>> 7);
    }
    
    private void lfsr_step() {
        this.instance.lfsr_step();
        System.arraycopy(this.current_mask, 1, this.next_mask, 0, this.BlockSize - 1);
    }
    
    @Override
    protected void init(final byte[] array, final byte[] npub) throws IllegalArgumentException {
        this.npub = npub;
        System.arraycopy(array, 0, this.expanded_key = new byte[this.BlockSize], 0, this.KEY_SIZE);
        this.instance.permutation(this.expanded_key);
    }
    
    @Override
    protected void processBufferEncrypt(final byte[] array, final int n, final byte[] array2, final int n2) {
        this.processBuffer(array, n, array2, n2, State.EncData);
        System.arraycopy(array2, n2, this.previous_outputMessage, 0, this.BlockSize);
    }
    
    private void processBuffer(final byte[] array, final int n, final byte[] array2, final int n2, final State state) {
        if (this.m_state == State.DecInit || this.m_state == State.EncInit) {
            this.processFinalAAD();
        }
        this.lfsr_step();
        this.computeCipherBlock(array, n, this.BlockSize, array2, n2);
        if (this.nb_its > 0) {
            System.arraycopy(this.previous_outputMessage, 0, this.buffer, 0, this.BlockSize);
            this.absorbCiphertext();
        }
        if (this.m_state != state) {
            this.absorbAAD();
        }
        this.swapMasks();
        ++this.nb_its;
    }
    
    @Override
    protected void processBufferDecrypt(final byte[] array, final int n, final byte[] array2, final int n2) {
        this.processBuffer(array, n, array2, n2, State.DecData);
        System.arraycopy(array, n, this.previous_outputMessage, 0, this.BlockSize);
    }
    
    private void computeCipherBlock(final byte[] array, final int n, final int n2, final byte[] array2, final int n3) {
        System.arraycopy(this.npub, 0, this.buffer, 0, this.IV_SIZE);
        Arrays.fill(this.buffer, this.IV_SIZE, this.BlockSize, (byte)0);
        xorTo(this.BlockSize, this.current_mask, this.next_mask, this.buffer);
        this.instance.permutation(this.buffer);
        xorTo(this.BlockSize, this.current_mask, this.next_mask, this.buffer);
        Bytes.xorTo(n2, array, n, this.buffer);
        System.arraycopy(this.buffer, 0, array2, n3, n2);
    }
    
    private void swapMasks() {
        final byte[] previous_mask = this.previous_mask;
        this.previous_mask = this.current_mask;
        this.current_mask = this.next_mask;
        this.next_mask = previous_mask;
    }
    
    private void absorbAAD() {
        this.processAADBytes(this.buffer);
        Bytes.xorTo(this.BlockSize, this.next_mask, this.buffer);
        this.instance.permutation(this.buffer);
        Bytes.xorTo(this.BlockSize, this.next_mask, this.buffer);
        Bytes.xorTo(this.BlockSize, this.buffer, this.tag_buffer);
    }
    
    private void absorbCiphertext() {
        xorTo(this.BlockSize, this.previous_mask, this.next_mask, this.buffer);
        this.instance.permutation(this.buffer);
        xorTo(this.BlockSize, this.previous_mask, this.next_mask, this.buffer);
        Bytes.xorTo(this.BlockSize, this.buffer, this.tag_buffer);
    }
    
    @Override
    protected void processFinalBlock(final byte[] array, final int n) {
        final int n2 = this.dataOperator.getLen() - (this.forEncryption ? 0 : this.MAC_SIZE);
        this.processFinalAAD();
        final int n3 = 1 + n2 / this.BlockSize;
        final int n4 = (n2 % this.BlockSize != 0) ? n3 : (n3 - 1);
        final int n5 = 1 + (this.IV_SIZE + this.adlen) / this.BlockSize;
        this.processBytes(this.m_buf, array, n, Math.max(n3 + 1, n5 - 1), n4, n3, n2, n5);
        Bytes.xorTo(this.BlockSize, this.expanded_key, this.tag_buffer);
        this.instance.permutation(this.tag_buffer);
        Bytes.xorTo(this.BlockSize, this.expanded_key, this.tag_buffer);
        System.arraycopy(this.tag_buffer, 0, this.mac, 0, this.MAC_SIZE);
    }
    
    @Override
    protected void processBufferAAD(final byte[] array, final int n) {
    }
    
    @Override
    public int getUpdateOutputSize(final int n) {
        switch (this.m_state.ord) {
            case 0: {
                throw new IllegalArgumentException(this.algorithmName + " needs call init function before getUpdateOutputSize");
            }
            case 4:
            case 8: {
                return 0;
            }
            case 1:
            case 2:
            case 3: {
                final int n2 = this.m_bufPos + n;
                return n2 - n2 % this.BlockSize;
            }
            case 5:
            case 6:
            case 7: {
                final int max = Math.max(0, this.m_bufPos + n - this.MAC_SIZE);
                return max - max % this.BlockSize;
            }
            default: {
                return Math.max(0, n + this.m_bufPos - this.MAC_SIZE);
            }
        }
    }
    
    @Override
    public int getOutputSize(final int n) {
        switch (this.m_state.ord) {
            case 0: {
                throw new IllegalArgumentException(this.algorithmName + " needs call init function before getUpdateOutputSize");
            }
            case 4:
            case 8: {
                return 0;
            }
            case 1:
            case 2:
            case 3: {
                return n + this.m_bufPos + this.MAC_SIZE;
            }
            default: {
                return Math.max(0, n + this.m_bufPos - this.MAC_SIZE);
            }
        }
    }
    
    @Override
    protected void finishAAD(final State state, final boolean b) {
        this.finishAAD2(state);
    }
    
    @Override
    protected void processFinalAAD() {
        if (this.adOff == -1) {
            this.ad = ((StreamAADOperator)this.aadOperator).getBytes();
            this.adOff = 0;
            this.adlen = this.aadOperator.getLen();
            this.aadOperator.reset();
        }
        switch (this.m_state.ord) {
            case 1:
            case 5: {
                this.processAADBytes(this.tag_buffer);
                break;
            }
        }
    }
    
    @Override
    protected void reset(final boolean b) {
        super.reset(b);
        Arrays.fill(this.tag_buffer, (byte)0);
        Arrays.fill(this.previous_outputMessage, (byte)0);
        this.nb_its = 0;
        this.adOff = -1;
    }
    
    @Override
    protected void checkAAD() {
        switch (this.m_state.ord) {
            case 7: {
                throw new IllegalArgumentException(this.algorithmName + " cannot process AAD when the length of the plaintext to be processed exceeds the a block size");
            }
            case 3: {
                throw new IllegalArgumentException(this.algorithmName + " cannot process AAD when the length of the ciphertext to be processed exceeds the a block size");
            }
            case 4: {
                throw new IllegalArgumentException(this.algorithmName + " cannot be reused for encryption");
            }
            default: {}
        }
    }
    
    @Override
    protected boolean checkData(final boolean b) {
        switch (this.m_state.ord) {
            case 5:
            case 6:
            case 7: {
                return false;
            }
            case 1:
            case 2:
            case 3: {
                return true;
            }
            case 4: {
                throw new IllegalStateException(this.getAlgorithmName() + " cannot be reused for encryption");
            }
            default: {
                throw new IllegalStateException(this.getAlgorithmName() + " needs to be initialized");
            }
        }
    }
    
    private void processAADBytes(final byte[] array) {
        int n = 0;
        switch (this.m_state.ord) {
            case 5: {
                System.arraycopy(this.expanded_key, 0, this.current_mask, 0, this.BlockSize);
                System.arraycopy(this.npub, 0, array, 0, this.IV_SIZE);
                n += this.IV_SIZE;
                this.m_state = State.DecAad;
                break;
            }
            case 1: {
                System.arraycopy(this.expanded_key, 0, this.current_mask, 0, this.BlockSize);
                System.arraycopy(this.npub, 0, array, 0, this.IV_SIZE);
                n += this.IV_SIZE;
                this.m_state = State.EncAad;
                break;
            }
            case 2:
            case 6: {
                if (this.adOff == this.adlen) {
                    Arrays.fill(array, 0, this.BlockSize, (byte)0);
                    array[0] = 1;
                    return;
                }
                break;
            }
        }
        final int n2 = this.BlockSize - n;
        final int n3 = this.adlen - this.adOff;
        if (n2 <= n3) {
            System.arraycopy(this.ad, this.adOff, array, n, n2);
            this.adOff += n2;
        }
        else {
            if (n3 > 0) {
                System.arraycopy(this.ad, this.adOff, array, n, n3);
                this.adOff += n3;
            }
            Arrays.fill(array, n + n3, n + n2, (byte)0);
            array[n + n3] = 1;
            switch (this.m_state.ord) {
                case 6: {
                    this.m_state = State.DecData;
                    break;
                }
                case 2: {
                    this.m_state = State.EncData;
                    break;
                }
            }
        }
    }
    
    private void processBytes(final byte[] array, final byte[] array2, int n, final int n2, final int n3, final int n4, final int n5, final int n6) {
        int n7 = 0;
        final byte[] array3 = new byte[this.BlockSize];
        int i;
        for (i = this.nb_its; i < n2; ++i) {
            final int n8 = (i == n3 - 1) ? (n5 - i * this.BlockSize) : this.BlockSize;
            this.lfsr_step();
            if (i < n3) {
                this.computeCipherBlock(array, n7, n8, array2, n);
                if (this.forEncryption) {
                    System.arraycopy(this.buffer, 0, array3, 0, n8);
                }
                else {
                    System.arraycopy(array, n7, array3, 0, n8);
                }
                n += n8;
                n7 += n8;
            }
            if (i > 0 && i <= n4) {
                final int n9 = (i - 1) * this.BlockSize;
                if (n9 == n5) {
                    Arrays.fill(this.buffer, 1, this.BlockSize, (byte)0);
                    this.buffer[0] = 1;
                }
                else {
                    final int fromIndex = n5 - n9;
                    if (this.BlockSize <= fromIndex) {
                        System.arraycopy(this.previous_outputMessage, 0, this.buffer, 0, this.BlockSize);
                    }
                    else if (fromIndex > 0) {
                        System.arraycopy(this.previous_outputMessage, 0, this.buffer, 0, fromIndex);
                        Arrays.fill(this.buffer, fromIndex, this.BlockSize, (byte)0);
                        this.buffer[fromIndex] = 1;
                    }
                }
                this.absorbCiphertext();
            }
            if (i + 1 < n6) {
                this.absorbAAD();
            }
            this.swapMasks();
            System.arraycopy(array3, 0, this.previous_outputMessage, 0, this.BlockSize);
        }
        this.nb_its = i;
    }
    
    public static void xorTo(final int n, final byte[] array, final byte[] array2, final byte[] array3) {
        for (int i = 0; i < n; ++i) {
            final int n2 = i;
            array3[n2] ^= (byte)(array[i] ^ array2[i]);
        }
    }
    
    private class Delirium implements Permutation
    {
        private static final int nRounds = 18;
        private final byte[] KeccakRoundConstants;
        private final int[] KeccakRhoOffsets;
        
        private Delirium() {
            this.KeccakRoundConstants = new byte[] { 1, -126, -118, 0, -117, 1, -127, 9, -118, -120, 9, 10, -117, -117, -119, 3, 2, -128 };
            this.KeccakRhoOffsets = new int[] { 0, 1, 6, 4, 3, 4, 4, 6, 7, 4, 3, 2, 3, 1, 7, 1, 5, 7, 5, 0, 2, 2, 5, 0, 6 };
        }
        
        @Override
        public void permutation(final byte[] array) {
            for (int i = 0; i < 18; ++i) {
                this.KeccakP200Round(array, i);
            }
        }
        
        @Override
        public void lfsr_step() {
            ElephantEngine.this.next_mask[ElephantEngine.this.BlockSize - 1] = (byte)(ElephantEngine.this.rotl(ElephantEngine.this.current_mask[0]) ^ ElephantEngine.this.rotl(ElephantEngine.this.current_mask[2]) ^ ElephantEngine.this.current_mask[13] << 1);
        }
        
        private void KeccakP200Round(final byte[] array, final int n) {
            final byte[] array2 = new byte[25];
            for (int i = 0; i < 5; ++i) {
                for (int j = 0; j < 5; ++j) {
                    final byte[] array3 = array2;
                    final int n2 = i;
                    array3[n2] ^= array[this.index(i, j)];
                }
            }
            for (int k = 0; k < 5; ++k) {
                array2[k + 5] = (byte)(this.ROL8(array2[(k + 1) % 5], 1) ^ array2[(k + 4) % 5]);
            }
            for (int l = 0; l < 5; ++l) {
                for (int n3 = 0; n3 < 5; ++n3) {
                    final int index = this.index(l, n3);
                    array[index] ^= array2[l + 5];
                }
            }
            for (int n4 = 0; n4 < 5; ++n4) {
                for (int n5 = 0; n5 < 5; ++n5) {
                    array2[this.index(n4, n5)] = this.ROL8(array[this.index(n4, n5)], this.KeccakRhoOffsets[this.index(n4, n5)]);
                }
            }
            for (int n6 = 0; n6 < 5; ++n6) {
                for (int n7 = 0; n7 < 5; ++n7) {
                    array[this.index(n7, (2 * n6 + 3 * n7) % 5)] = array2[this.index(n6, n7)];
                }
            }
            for (int n8 = 0; n8 < 5; ++n8) {
                for (int n9 = 0; n9 < 5; ++n9) {
                    array2[n9] = (byte)(array[this.index(n9, n8)] ^ (~array[this.index((n9 + 1) % 5, n8)] & array[this.index((n9 + 2) % 5, n8)]));
                }
                for (int n10 = 0; n10 < 5; ++n10) {
                    array[this.index(n10, n8)] = array2[n10];
                }
            }
            final int n11 = 0;
            array[n11] ^= this.KeccakRoundConstants[n];
        }
        
        private byte ROL8(final byte b, final int n) {
            return (byte)(b << n | (b & 0xFF) >>> 8 - n);
        }
        
        private int index(final int n, final int n2) {
            return n + n2 * 5;
        }
    }
    
    private interface Permutation
    {
        void permutation(final byte[] p0);
        
        void lfsr_step();
    }
    
    private class Dumbo extends Spongent
    {
        public Dumbo() {
            super(160, 20, 80, (byte)117);
        }
        
        @Override
        public void lfsr_step() {
            ElephantEngine.this.next_mask[ElephantEngine.this.BlockSize - 1] = (byte)(((ElephantEngine.this.current_mask[0] & 0xFF) << 3 | (ElephantEngine.this.current_mask[0] & 0xFF) >>> 5) ^ (ElephantEngine.this.current_mask[3] & 0xFF) << 7 ^ (ElephantEngine.this.current_mask[13] & 0xFF) >>> 7);
        }
    }
    
    private abstract static class Spongent implements Permutation
    {
        private final byte lfsrIV;
        private final int nRounds;
        private final int nBits;
        private final int nSBox;
        private final byte[] sBoxLayer;
        
        public Spongent(final int nBits, final int nsBox, final int nRounds, final byte lfsrIV) {
            this.sBoxLayer = new byte[] { -18, -19, -21, -32, -30, -31, -28, -17, -25, -22, -24, -27, -23, -20, -29, -26, -34, -35, -37, -48, -46, -47, -44, -33, -41, -38, -40, -43, -39, -36, -45, -42, -66, -67, -69, -80, -78, -79, -76, -65, -73, -70, -72, -75, -71, -68, -77, -74, 14, 13, 11, 0, 2, 1, 4, 15, 7, 10, 8, 5, 9, 12, 3, 6, 46, 45, 43, 32, 34, 33, 36, 47, 39, 42, 40, 37, 41, 44, 35, 38, 30, 29, 27, 16, 18, 17, 20, 31, 23, 26, 24, 21, 25, 28, 19, 22, 78, 77, 75, 64, 66, 65, 68, 79, 71, 74, 72, 69, 73, 76, 67, 70, -2, -3, -5, -16, -14, -15, -12, -1, -9, -6, -8, -11, -7, -4, -13, -10, 126, 125, 123, 112, 114, 113, 116, 127, 119, 122, 120, 117, 121, 124, 115, 118, -82, -83, -85, -96, -94, -95, -92, -81, -89, -86, -88, -91, -87, -84, -93, -90, -114, -115, -117, -128, -126, -127, -124, -113, -121, -118, -120, -123, -119, -116, -125, -122, 94, 93, 91, 80, 82, 81, 84, 95, 87, 90, 88, 85, 89, 92, 83, 86, -98, -99, -101, -112, -110, -111, -108, -97, -105, -102, -104, -107, -103, -100, -109, -106, -50, -51, -53, -64, -62, -63, -60, -49, -57, -54, -56, -59, -55, -52, -61, -58, 62, 61, 59, 48, 50, 49, 52, 63, 55, 58, 56, 53, 57, 60, 51, 54, 110, 109, 107, 96, 98, 97, 100, 111, 103, 106, 104, 101, 105, 108, 99, 102 };
            this.nRounds = nRounds;
            this.nSBox = nsBox;
            this.lfsrIV = lfsrIV;
            this.nBits = nBits;
        }
        
        @Override
        public void permutation(final byte[] array) {
            byte lfsrIV = this.lfsrIV;
            final byte[] a = new byte[this.nSBox];
            for (int i = 0; i < this.nRounds; ++i) {
                final int n = 0;
                array[n] ^= lfsrIV;
                final int n2 = this.nSBox - 1;
                array[n2] ^= (byte)((lfsrIV & 0x1) << 7 | (lfsrIV & 0x2) << 5 | (lfsrIV & 0x4) << 3 | (lfsrIV & 0x8) << 1 | (lfsrIV & 0x10) >>> 1 | (lfsrIV & 0x20) >>> 3 | (lfsrIV & 0x40) >>> 5 | (lfsrIV & 0x80) >>> 7);
                lfsrIV = (byte)((lfsrIV << 1 | ((0x40 & lfsrIV) >>> 6 ^ (0x20 & lfsrIV) >>> 5)) & 0x7F);
                for (int j = 0; j < this.nSBox; ++j) {
                    array[j] = this.sBoxLayer[array[j] & 0xFF];
                }
                Arrays.fill(a, (byte)0);
                for (int k = 0; k < this.nSBox; ++k) {
                    for (int l = 0; l < 8; ++l) {
                        int n3 = (k << 3) + l;
                        if (n3 != this.nBits - 1) {
                            n3 = (n3 * this.nBits >> 2) % (this.nBits - 1);
                        }
                        final byte[] array2 = a;
                        final int n4 = n3 >>> 3;
                        array2[n4] ^= (byte)(((array[k] & 0xFF) >>> l & 0x1) << (n3 & 0x7));
                    }
                }
                System.arraycopy(a, 0, array, 0, this.nSBox);
            }
        }
    }
    
    public enum ElephantParameters
    {
        elephant160, 
        elephant176, 
        elephant200;
    }
    
    private class Jumbo extends Spongent
    {
        public Jumbo() {
            super(176, 22, 90, (byte)69);
        }
        
        @Override
        public void lfsr_step() {
            ElephantEngine.this.next_mask[ElephantEngine.this.BlockSize - 1] = (byte)(ElephantEngine.this.rotl(ElephantEngine.this.current_mask[0]) ^ (ElephantEngine.this.current_mask[3] & 0xFF) << 7 ^ (ElephantEngine.this.current_mask[19] & 0xFF) >>> 7);
        }
    }
}
