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

package org.bouncycastle.pqc.crypto.hqc;

import org.bouncycastle.util.Pack;
import org.bouncycastle.crypto.digests.SHA3Digest;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.Longs;
import java.security.SecureRandom;

class HQCEngine
{
    private final int n;
    private final int n1;
    private final int k;
    private final int delta;
    private final int w;
    private final int wr;
    private final int g;
    private final int fft;
    private final int mulParam;
    private static final int SEED_BYTES = 32;
    private final int N_BYTE;
    private final int N_BYTE_64;
    private final int K_BYTE;
    private final int N1N2_BYTE_64;
    private final int N1N2_BYTE;
    private static final int SALT_SIZE_BYTES = 16;
    private final int[] generatorPoly;
    private final int N_MU;
    private final int pkSize;
    private final GF2PolynomialCalculator gf;
    private final long rejectionThreshold;
    
    public HQCEngine(final int n, final int n2, final int n3, final int n4, final int g, final int delta, final int w, final int wr, final int fft, final int n_MU, final int pkSize, final int[] generatorPoly) {
        this.n = n;
        this.k = n4;
        this.delta = delta;
        this.w = w;
        this.wr = wr;
        this.n1 = n2;
        this.generatorPoly = generatorPoly;
        this.g = g;
        this.fft = fft;
        this.N_MU = n_MU;
        this.pkSize = pkSize;
        this.mulParam = n3 >> 7;
        this.N_BYTE = Utils.getByteSizeFromBitSize(n);
        this.K_BYTE = n4;
        this.N_BYTE_64 = Utils.getByte64SizeFromBitSize(n);
        this.N1N2_BYTE_64 = Utils.getByte64SizeFromBitSize(n2 * n3);
        this.N1N2_BYTE = Utils.getByteSizeFromBitSize(n2 * n3);
        this.gf = new GF2PolynomialCalculator(this.N_BYTE_64, n, (1L << (n & 0x3F)) - 1L);
        this.rejectionThreshold = 16777216L / n * n;
    }
    
    public void genKeyPair(final byte[] array, final byte[] array2, final SecureRandom secureRandom) {
        final byte[] bytes = new byte[32];
        final byte[] array3 = new byte[64];
        final long[] array4 = new long[this.N_BYTE_64];
        final long[] array5 = new long[this.N_BYTE_64];
        final long[] array6 = new long[this.N_BYTE_64];
        secureRandom.nextBytes(bytes);
        final Shake256RandomGenerator shake256RandomGenerator = new Shake256RandomGenerator(bytes, (byte)1);
        System.arraycopy(bytes, 0, array2, this.pkSize + 32 + this.K_BYTE, 32);
        shake256RandomGenerator.nextBytes(bytes);
        shake256RandomGenerator.nextBytes(array2, this.pkSize + 32, this.K_BYTE);
        hashHI(array3, 512, bytes, bytes.length, (byte)2);
        shake256RandomGenerator.init(array3, 0, 32, (byte)1);
        this.vectSampleFixedWeight1(array5, shake256RandomGenerator, this.w);
        this.vectSampleFixedWeight1(array4, shake256RandomGenerator, this.w);
        System.arraycopy(array3, 32, array, 0, 32);
        shake256RandomGenerator.init(array3, 32, 32, (byte)1);
        this.vectSetRandom(shake256RandomGenerator, array6);
        this.gf.vectMul(array6, array5, array6);
        Longs.xorTo(this.N_BYTE_64, array4, 0, array6, 0);
        Utils.fromLongArrayToByteArray(array, 32, array.length - 32, array6);
        System.arraycopy(array3, 0, array2, this.pkSize, 32);
        System.arraycopy(array, 0, array2, 0, this.pkSize);
        Arrays.clear(array3);
        Arrays.clear(array4);
        Arrays.clear(array5);
        Arrays.clear(array6);
    }
    
    public void encaps(final byte[] array, final byte[] array2, final byte[] array3, final byte[] array4, final byte[] bytes, final SecureRandom secureRandom) {
        final byte[] bytes2 = new byte[this.K_BYTE];
        final byte[] array5 = new byte[32];
        final long[] array6 = new long[this.N_BYTE_64];
        final long[] array7 = new long[this.N1N2_BYTE_64];
        secureRandom.nextBytes(bytes2);
        secureRandom.nextBytes(bytes);
        hashHI(array5, 256, array4, array4.length, (byte)1);
        this.hashGJ(array3, 512, array5, bytes2, 0, bytes2.length, bytes, 0, 16, (byte)0);
        this.pkeEncrypt(array6, array7, array4, bytes2, array3, 32);
        Utils.fromLongArrayToByteArray(array, array6);
        Utils.fromLongArrayToByteArray(array2, array7);
        Arrays.clear(array6);
        Arrays.clear(array7);
        Arrays.clear(bytes2);
        Arrays.clear(array5);
    }
    
    public int decaps(final byte[] array, final byte[] array2, final byte[] array3) {
        final long[] array4 = new long[this.N_BYTE_64];
        final long[] array5 = new long[this.N_BYTE_64];
        final long[] array6 = new long[this.N_BYTE_64];
        final long[] array7 = new long[this.N_BYTE_64];
        final byte[] array8 = new byte[32];
        final byte[] array9 = new byte[64];
        final byte[] array10 = new byte[this.k];
        final byte[] array11 = new byte[32];
        final byte[] array12 = new byte[this.n1];
        this.vectSampleFixedWeight1(array7, new Shake256RandomGenerator(array3, this.pkSize, 32, (byte)1), this.w);
        Utils.fromByteArrayToLongArray(array4, array2, 0, this.N_BYTE);
        Utils.fromByteArrayToLongArray(array5, array2, this.N_BYTE, this.N1N2_BYTE);
        this.gf.vectMul(array6, array7, array4);
        this.vectTruncate(array6);
        Longs.xorTo(this.N_BYTE_64, array5, 0, array6, 0);
        ReedMuller.decode(array12, array6, this.n1, this.mulParam);
        ReedSolomon.decode(array10, array12, this.n1, this.fft, this.delta, this.k, this.g);
        byte b = 0;
        hashHI(array8, 256, array3, this.pkSize, (byte)1);
        this.hashGJ(array9, 512, array8, array10, 0, array10.length, array2, this.N_BYTE + this.N1N2_BYTE, 16, (byte)0);
        System.arraycopy(array9, 0, array, 0, 32);
        Arrays.fill(array7, 0L);
        this.pkeEncrypt(array6, array7, array3, array10, array9, 32);
        this.hashGJ(array11, 256, array8, array3, this.pkSize + 32, this.K_BYTE, array2, 0, array2.length, (byte)3);
        if (!Arrays.constantTimeAreEqual(this.N_BYTE_64, array4, 0, array6, 0)) {
            b = 1;
        }
        if (!Arrays.constantTimeAreEqual(this.N_BYTE_64, array5, 0, array7, 0)) {
            b = 1;
        }
        --b;
        for (int i = 0; i < this.K_BYTE; ++i) {
            array[i] = (byte)(((array[i] & b) ^ (array11[i] & ~b)) & 0xFF);
        }
        Arrays.clear(array4);
        Arrays.clear(array5);
        Arrays.clear(array6);
        Arrays.clear(array7);
        Arrays.clear(array8);
        Arrays.clear(array9);
        Arrays.clear(array10);
        Arrays.clear(array11);
        Arrays.clear(array12);
        return -b;
    }
    
    private void pkeEncrypt(final long[] array, final long[] array2, final byte[] array3, final byte[] array4, final byte[] array5, final int n) {
        final long[] array6 = new long[this.N_BYTE_64];
        final long[] array7 = new long[this.N_BYTE_64];
        final byte[] array8 = new byte[this.n1];
        ReedSolomon.encode(array8, array4, this.n1, this.k, this.g, this.generatorPoly);
        ReedMuller.encode(array2, array8, this.n1, this.mulParam);
        final Shake256RandomGenerator shake256RandomGenerator = new Shake256RandomGenerator(array3, 0, 32, (byte)1);
        this.vectSetRandom(shake256RandomGenerator, array7);
        shake256RandomGenerator.init(array5, n, 32, (byte)1);
        this.vectSampleFixedWeights2(shake256RandomGenerator, array6, this.wr);
        this.gf.vectMul(array, array6, array7);
        Utils.fromByteArrayToLongArray(array7, array3, 32, this.pkSize - 32);
        this.gf.vectMul(array7, array6, array7);
        this.vectSampleFixedWeights2(shake256RandomGenerator, array6, this.wr);
        Longs.xorTo(this.N_BYTE_64, array6, 0, array7, 0);
        this.vectTruncate(array7);
        Longs.xorTo(this.N1N2_BYTE_64, array7, 0, array2, 0);
        this.vectSampleFixedWeights2(shake256RandomGenerator, array7, this.wr);
        Longs.xorTo(this.N_BYTE_64, array7, 0, array, 0);
        Arrays.clear(array6);
        Arrays.clear(array7);
        Arrays.clear(array8);
    }
    
    private int barrettReduce(final int n) {
        final int n2 = n - (int)((n * (long)this.N_MU >>> 32) * this.n);
        return n2 - (-(n2 - this.n >>> 31 ^ 0x1) & this.n);
    }
    
    private void generateRandomSupport(final int[] array, final int n, final Shake256RandomGenerator shake256RandomGenerator) {
        final int n2 = 3 * n;
        final byte[] array2 = new byte[n2];
        int n3 = n2;
        int i = 0;
        while (i < n) {
            if (n3 == n2) {
                shake256RandomGenerator.xofGetBytes(array2, n2);
                n3 = 0;
            }
            final int n4 = (array2[n3++] & 0xFF) << 16 | (array2[n3++] & 0xFF) << 8 | (array2[n3++] & 0xFF);
            if (n4 >= this.rejectionThreshold) {
                continue;
            }
            final int barrettReduce = this.barrettReduce(n4);
            boolean b = false;
            for (int j = 0; j < i; ++j) {
                if (array[j] == barrettReduce) {
                    b = true;
                    break;
                }
            }
            if (b) {
                continue;
            }
            array[i++] = barrettReduce;
        }
    }
    
    private void writeSupportToVector(final long[] array, final int[] array2, final int n) {
        final int[] array3 = new int[this.wr];
        final long[] array4 = new long[this.wr];
        for (int i = 0; i < n; ++i) {
            array3[i] = array2[i] >>> 6;
            array4[i] = 1L << (array2[i] & 0x3F);
        }
        for (int j = 0; j < array.length; ++j) {
            long n2 = 0L;
            for (int k = 0; k < n; ++k) {
                final int n3 = j - array3[k];
                n2 |= (array4[k] & (long)(-(0x1 ^ (n3 | -n3) >>> 31)));
            }
            array[j] = n2;
        }
    }
    
    public void vectSampleFixedWeight1(final long[] array, final Shake256RandomGenerator shake256RandomGenerator, final int n) {
        final int[] array2 = new int[this.wr];
        this.generateRandomSupport(array2, n, shake256RandomGenerator);
        this.writeSupportToVector(array, array2, n);
    }
    
    private static void hashHI(final byte[] array, final int n, final byte[] array2, final int n2, final byte b) {
        final SHA3Digest sha3Digest = new SHA3Digest(n);
        sha3Digest.update(array2, 0, n2);
        sha3Digest.update(b);
        sha3Digest.doFinal(array, 0);
    }
    
    private void hashGJ(final byte[] array, final int n, final byte[] array2, final byte[] array3, final int n2, final int n3, final byte[] array4, final int n4, final int n5, final byte b) {
        final SHA3Digest sha3Digest = new SHA3Digest(n);
        sha3Digest.update(array2, 0, array2.length);
        sha3Digest.update(array3, n2, n3);
        sha3Digest.update(array4, n4, n5);
        sha3Digest.update(b);
        sha3Digest.doFinal(array, 0);
    }
    
    private void vectSetRandom(final Shake256RandomGenerator shake256RandomGenerator, final long[] array) {
        final byte[] array2 = new byte[array.length << 3];
        shake256RandomGenerator.xofGetBytes(array2, this.N_BYTE);
        Pack.littleEndianToLong(array2, 0, array);
        final int n = this.N_BYTE_64 - 1;
        array[n] &= Utils.bitMask(this.n, 64L);
    }
    
    private void vectSampleFixedWeights2(final Shake256RandomGenerator shake256RandomGenerator, final long[] array, final int n) {
        final int[] array2 = new int[this.wr];
        final byte[] array3 = new byte[this.wr << 2];
        shake256RandomGenerator.xofGetBytes(array3, array3.length);
        Pack.littleEndianToInt(array3, 0, array2);
        for (int i = 0; i < n; ++i) {
            array2[i] = i + (int)(((long)array2[i] & 0xFFFFFFFFL) * (this.n - i) >> 32);
        }
        int n2 = n - 1;
        while (n2-- > 0) {
            int n3 = 0;
            for (int j = n2 + 1; j < n; ++j) {
                n3 |= compareU32(array2[j], array2[n2]);
            }
            final int n4 = -n3;
            array2[n2] = ((n4 & n2) ^ (~n4 & array2[n2]));
        }
        this.writeSupportToVector(array, array2, n);
    }
    
    private static int compareU32(final int n, final int n2) {
        return 0x1 ^ (n - n2 | n2 - n) >>> 31;
    }
    
    private void vectTruncate(final long[] array) {
        Arrays.fill(array, this.N1N2_BYTE_64, this.n + 63 >> 6, 0L);
    }
}
