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

package org.bouncycastle.crypto.digests;

import org.bouncycastle.util.Integers;
import org.bouncycastle.util.Pack;
import java.util.Iterator;
import org.bouncycastle.crypto.OutputLengthException;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.crypto.params.Blake3Parameters;
import org.bouncycastle.crypto.CryptoServicesRegistrar;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.CryptoServicePurpose;
import java.util.Stack;
import org.bouncycastle.crypto.Xof;
import org.bouncycastle.util.Memoable;
import org.bouncycastle.crypto.ExtendedDigest;

public class Blake3Digest implements ExtendedDigest, Memoable, Xof
{
    private static final String ERR_OUTPUTTING = "Already outputting";
    private static final int NUMWORDS = 8;
    private static final int ROUNDS = 7;
    private static final int BLOCKLEN = 64;
    private static final int CHUNKLEN = 1024;
    private static final int CHUNKSTART = 1;
    private static final int CHUNKEND = 2;
    private static final int PARENT = 4;
    private static final int ROOT = 8;
    private static final int KEYEDHASH = 16;
    private static final int DERIVECONTEXT = 32;
    private static final int DERIVEKEY = 64;
    private static final int CHAINING0 = 0;
    private static final int CHAINING1 = 1;
    private static final int CHAINING2 = 2;
    private static final int CHAINING3 = 3;
    private static final int CHAINING4 = 4;
    private static final int CHAINING5 = 5;
    private static final int CHAINING6 = 6;
    private static final int CHAINING7 = 7;
    private static final int IV0 = 8;
    private static final int IV1 = 9;
    private static final int IV2 = 10;
    private static final int IV3 = 11;
    private static final int COUNT0 = 12;
    private static final int COUNT1 = 13;
    private static final int DATALEN = 14;
    private static final int FLAGS = 15;
    private static final byte[] SIGMA;
    private static final int[] IV;
    private final byte[] theBuffer;
    private final int[] theK;
    private final int[] theChaining;
    private final int[] theV;
    private final int[] theM;
    private final byte[] theIndices;
    private final Stack theStack;
    private final int theDigestLen;
    private boolean outputting;
    private long outputAvailable;
    private int theMode;
    private int theOutputMode;
    private int theOutputDataLen;
    private long theCounter;
    private int theCurrBytes;
    private int thePos;
    private final CryptoServicePurpose purpose;
    
    public Blake3Digest() {
        this(256);
    }
    
    public Blake3Digest(final int n) {
        this((n > 100) ? n : (n * 8), CryptoServicePurpose.ANY);
    }
    
    public Blake3Digest(final int n, final CryptoServicePurpose purpose) {
        this.theBuffer = new byte[64];
        this.theK = new int[8];
        this.theChaining = new int[8];
        this.theV = new int[16];
        this.theM = new int[16];
        this.theIndices = new byte[16];
        this.theStack = new Stack();
        this.purpose = purpose;
        this.theDigestLen = n / 8;
        CryptoServicesRegistrar.checkConstraints(Utils.getDefaultProperties(this, this.getDigestSize() * 8, purpose));
        this.init(null);
    }
    
    public Blake3Digest(final Blake3Digest blake3Digest) {
        this.theBuffer = new byte[64];
        this.theK = new int[8];
        this.theChaining = new int[8];
        this.theV = new int[16];
        this.theM = new int[16];
        this.theIndices = new byte[16];
        this.theStack = new Stack();
        this.theDigestLen = blake3Digest.theDigestLen;
        this.purpose = blake3Digest.purpose;
        this.reset(blake3Digest);
    }
    
    @Override
    public int getByteLength() {
        return 64;
    }
    
    @Override
    public String getAlgorithmName() {
        return "BLAKE3";
    }
    
    @Override
    public int getDigestSize() {
        return this.theDigestLen;
    }
    
    public void init(final Blake3Parameters blake3Parameters) {
        final byte[] array = (byte[])((blake3Parameters == null) ? null : blake3Parameters.getKey());
        final byte[] array2 = (byte[])((blake3Parameters == null) ? null : blake3Parameters.getContext());
        this.reset();
        if (array != null) {
            this.initKey(array);
            Arrays.fill(array, (byte)0);
        }
        else if (array2 != null) {
            this.initNullKey();
            this.theMode = 32;
            this.update(array2, 0, array2.length);
            this.doFinal(this.theBuffer, 0);
            this.initKeyFromContext();
            this.reset();
        }
        else {
            this.initNullKey();
            this.theMode = 0;
        }
    }
    
    @Override
    public void update(final byte b) {
        if (this.outputting) {
            throw new IllegalStateException("Already outputting");
        }
        if (this.theBuffer.length - this.thePos == 0) {
            this.compressBlock(this.theBuffer, 0);
            Arrays.fill(this.theBuffer, (byte)0);
            this.thePos = 0;
        }
        this.theBuffer[this.thePos] = b;
        ++this.thePos;
    }
    
    @Override
    public void update(final byte[] array, final int n, final int n2) {
        if (array == null || n2 == 0) {
            return;
        }
        if (this.outputting) {
            throw new IllegalStateException("Already outputting");
        }
        int n3 = 0;
        if (this.thePos != 0) {
            n3 = 64 - this.thePos;
            if (n3 >= n2) {
                System.arraycopy(array, n, this.theBuffer, this.thePos, n2);
                this.thePos += n2;
                return;
            }
            System.arraycopy(array, n, this.theBuffer, this.thePos, n3);
            this.compressBlock(this.theBuffer, 0);
            this.thePos = 0;
            Arrays.fill(this.theBuffer, (byte)0);
        }
        int n4;
        int i;
        for (n4 = n + n2 - 64, i = n + n3; i < n4; i += 64) {
            this.compressBlock(array, i);
        }
        final int n5 = n2 - i;
        System.arraycopy(array, i, this.theBuffer, 0, n + n5);
        this.thePos += n + n5;
    }
    
    @Override
    public int doFinal(final byte[] array, final int n) {
        return this.doFinal(array, n, this.getDigestSize());
    }
    
    @Override
    public int doFinal(final byte[] array, final int n, final int n2) {
        final int doOutput = this.doOutput(array, n, n2);
        this.reset();
        return doOutput;
    }
    
    @Override
    public int doOutput(final byte[] array, final int n, final int n2) {
        if (n > array.length - n2) {
            throw new OutputLengthException("output buffer too short");
        }
        if (!this.outputting) {
            this.compressFinalBlock(this.thePos);
        }
        if (n2 < 0 || (this.outputAvailable >= 0L && n2 > this.outputAvailable)) {
            throw new IllegalArgumentException("Insufficient bytes remaining");
        }
        int i = n2;
        int n3 = n;
        if (this.thePos < 64) {
            final int min = Math.min(i, 64 - this.thePos);
            System.arraycopy(this.theBuffer, this.thePos, array, n3, min);
            this.thePos += min;
            n3 += min;
            i -= min;
        }
        while (i > 0) {
            this.nextOutputBlock();
            final int min2 = Math.min(i, 64);
            System.arraycopy(this.theBuffer, 0, array, n3, min2);
            this.thePos += min2;
            n3 += min2;
            i -= min2;
        }
        this.outputAvailable -= n2;
        return n2;
    }
    
    @Override
    public void reset() {
        this.resetBlockCount();
        this.thePos = 0;
        this.outputting = false;
        Arrays.fill(this.theBuffer, (byte)0);
    }
    
    @Override
    public void reset(final Memoable memoable) {
        final Blake3Digest blake3Digest = (Blake3Digest)memoable;
        this.theCounter = blake3Digest.theCounter;
        this.theCurrBytes = blake3Digest.theCurrBytes;
        this.theMode = blake3Digest.theMode;
        this.outputting = blake3Digest.outputting;
        this.outputAvailable = blake3Digest.outputAvailable;
        this.theOutputMode = blake3Digest.theOutputMode;
        this.theOutputDataLen = blake3Digest.theOutputDataLen;
        System.arraycopy(blake3Digest.theChaining, 0, this.theChaining, 0, this.theChaining.length);
        System.arraycopy(blake3Digest.theK, 0, this.theK, 0, this.theK.length);
        System.arraycopy(blake3Digest.theM, 0, this.theM, 0, this.theM.length);
        this.theStack.clear();
        final Iterator iterator = blake3Digest.theStack.iterator();
        while (iterator.hasNext()) {
            this.theStack.push(Arrays.clone((int[])iterator.next()));
        }
        System.arraycopy(blake3Digest.theBuffer, 0, this.theBuffer, 0, this.theBuffer.length);
        this.thePos = blake3Digest.thePos;
    }
    
    @Override
    public Memoable copy() {
        return new Blake3Digest(this);
    }
    
    private void compressBlock(final byte[] array, final int n) {
        this.initChunkBlock(64, false);
        this.initM(array, n);
        this.compress();
        if (this.theCurrBytes == 0) {
            this.adjustStack();
        }
    }
    
    private void adjustStack() {
        for (long theCounter = this.theCounter; theCounter > 0L && (theCounter & 0x1L) != 0x1L; theCounter >>= 1) {
            System.arraycopy(this.theStack.pop(), 0, this.theM, 0, 8);
            System.arraycopy(this.theChaining, 0, this.theM, 8, 8);
            this.initParentBlock();
            this.compress();
        }
        this.theStack.push(Arrays.copyOf(this.theChaining, 8));
    }
    
    private void compressFinalBlock(final int n) {
        this.initChunkBlock(n, true);
        this.initM(this.theBuffer, 0);
        this.compress();
        this.processStack();
    }
    
    private void processStack() {
        while (!this.theStack.isEmpty()) {
            System.arraycopy(this.theStack.pop(), 0, this.theM, 0, 8);
            System.arraycopy(this.theChaining, 0, this.theM, 8, 8);
            this.initParentBlock();
            if (this.theStack.isEmpty()) {
                this.setRoot();
            }
            this.compress();
        }
    }
    
    private void compress() {
        this.initIndices();
        for (int i = 0; i < 6; ++i) {
            this.performRound();
            this.permuteIndices();
        }
        this.performRound();
        this.adjustChaining();
    }
    
    private void performRound() {
        this.mixG(0, 0, 4, 8, 12);
        this.mixG(1, 1, 5, 9, 13);
        this.mixG(2, 2, 6, 10, 14);
        this.mixG(3, 3, 7, 11, 15);
        this.mixG(4, 0, 5, 10, 15);
        this.mixG(5, 1, 6, 11, 12);
        this.mixG(6, 2, 7, 8, 13);
        this.mixG(7, 3, 4, 9, 14);
    }
    
    private void initM(final byte[] array, final int n) {
        Pack.littleEndianToInt(array, n, this.theM);
    }
    
    private void adjustChaining() {
        if (this.outputting) {
            for (int i = 0; i < 8; ++i) {
                final int[] theV = this.theV;
                final int n = i;
                theV[n] ^= this.theV[i + 8];
                final int[] theV2 = this.theV;
                final int n2 = i + 8;
                theV2[n2] ^= this.theChaining[i];
            }
            Pack.intToLittleEndian(this.theV, this.theBuffer, 0);
            this.thePos = 0;
        }
        else {
            for (int j = 0; j < 8; ++j) {
                this.theChaining[j] = (this.theV[j] ^ this.theV[j + 8]);
            }
        }
    }
    
    private void mixG(final int n, final int n2, final int n3, final int n4, final int n5) {
        int n6 = n << 1;
        final int[] theV = this.theV;
        theV[n2] += this.theV[n3] + this.theM[this.theIndices[n6++]];
        this.theV[n5] = Integers.rotateRight(this.theV[n5] ^ this.theV[n2], 16);
        final int[] theV2 = this.theV;
        theV2[n4] += this.theV[n5];
        this.theV[n3] = Integers.rotateRight(this.theV[n3] ^ this.theV[n4], 12);
        final int[] theV3 = this.theV;
        theV3[n2] += this.theV[n3] + this.theM[this.theIndices[n6]];
        this.theV[n5] = Integers.rotateRight(this.theV[n5] ^ this.theV[n2], 8);
        final int[] theV4 = this.theV;
        theV4[n4] += this.theV[n5];
        this.theV[n3] = Integers.rotateRight(this.theV[n3] ^ this.theV[n4], 7);
    }
    
    private void initIndices() {
        for (byte b = 0; b < this.theIndices.length; ++b) {
            this.theIndices[b] = b;
        }
    }
    
    private void permuteIndices() {
        for (int i = 0; i < this.theIndices.length; i = (byte)(i + 1)) {
            this.theIndices[i] = Blake3Digest.SIGMA[this.theIndices[i]];
        }
    }
    
    private void initNullKey() {
        System.arraycopy(Blake3Digest.IV, 0, this.theK, 0, 8);
    }
    
    private void initKey(final byte[] array) {
        Pack.littleEndianToInt(array, 0, this.theK);
        this.theMode = 16;
    }
    
    private void initKeyFromContext() {
        System.arraycopy(this.theV, 0, this.theK, 0, 8);
        this.theMode = 64;
    }
    
    private void initChunkBlock(final int n, final boolean b) {
        System.arraycopy((this.theCurrBytes == 0) ? this.theK : this.theChaining, 0, this.theV, 0, 8);
        System.arraycopy(Blake3Digest.IV, 0, this.theV, 8, 4);
        this.theV[12] = (int)this.theCounter;
        this.theV[13] = (int)(this.theCounter >> 32);
        this.theV[14] = n;
        this.theV[15] = this.theMode + ((this.theCurrBytes == 0) ? 1 : 0) + (b ? 2 : 0);
        this.theCurrBytes += n;
        if (this.theCurrBytes >= 1024) {
            this.incrementBlockCount();
            final int[] theV = this.theV;
            final int n2 = 15;
            theV[n2] |= 0x2;
        }
        if (b && this.theStack.isEmpty()) {
            this.setRoot();
        }
    }
    
    private void initParentBlock() {
        System.arraycopy(this.theK, 0, this.theV, 0, 8);
        System.arraycopy(Blake3Digest.IV, 0, this.theV, 8, 4);
        this.theV[12] = 0;
        this.theV[13] = 0;
        this.theV[14] = 64;
        this.theV[15] = (this.theMode | 0x4);
    }
    
    private void nextOutputBlock() {
        ++this.theCounter;
        System.arraycopy(this.theChaining, 0, this.theV, 0, 8);
        System.arraycopy(Blake3Digest.IV, 0, this.theV, 8, 4);
        this.theV[12] = (int)this.theCounter;
        this.theV[13] = (int)(this.theCounter >> 32);
        this.theV[14] = this.theOutputDataLen;
        this.theV[15] = this.theOutputMode;
        this.compress();
    }
    
    private void incrementBlockCount() {
        ++this.theCounter;
        this.theCurrBytes = 0;
    }
    
    private void resetBlockCount() {
        this.theCounter = 0L;
        this.theCurrBytes = 0;
    }
    
    private void setRoot() {
        final int[] theV = this.theV;
        final int n = 15;
        theV[n] |= 0x8;
        this.theOutputMode = this.theV[15];
        this.theOutputDataLen = this.theV[14];
        this.theCounter = 0L;
        this.outputting = true;
        this.outputAvailable = -1L;
        System.arraycopy(this.theV, 0, this.theChaining, 0, 8);
    }
    
    static {
        SIGMA = new byte[] { 2, 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8 };
        IV = new int[] { 1779033703, -1150833019, 1013904242, -1521486534, 1359893119, -1694144372, 528734635, 1541459225 };
    }
}
