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

package org.bouncycastle.pqc.crypto.picnic;

import org.bouncycastle.math.raw.Bits;
import java.security.SecureRandom;
import org.bouncycastle.util.Integers;
import org.bouncycastle.util.Pack;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.crypto.digests.SHAKEDigest;
import org.bouncycastle.crypto.Xof;
import java.util.logging.Logger;

class PicnicEngine
{
    private static final Logger LOG;
    protected static final int saltSizeBytes = 32;
    private static final int MAX_DIGEST_SIZE = 64;
    private static final int WORD_SIZE_BITS = 32;
    private static final int LOWMC_MAX_STATE_SIZE = 64;
    protected static final int LOWMC_MAX_WORDS = 16;
    protected static final int LOWMC_MAX_KEY_BITS = 256;
    protected static final int LOWMC_MAX_AND_GATES = 1144;
    private static final int MAX_AUX_BYTES = 176;
    private static final int PICNIC_MAX_LOWMC_BLOCK_SIZE = 32;
    private static final int TRANSFORM_FS = 0;
    private static final int TRANSFORM_UR = 1;
    private static final int TRANSFORM_INVALID = 255;
    private final int CRYPTO_SECRETKEYBYTES;
    private final int CRYPTO_PUBLICKEYBYTES;
    private final int CRYPTO_BYTES;
    protected final int numRounds;
    protected final int numSboxes;
    protected final int stateSizeBits;
    protected final int stateSizeBytes;
    protected final int stateSizeWords;
    protected final int andSizeBytes;
    protected final int UnruhGWithoutInputBytes;
    protected final int UnruhGWithInputBytes;
    protected final int numMPCRounds;
    protected final int numOpenedRounds;
    protected final int numMPCParties;
    protected final int seedSizeBytes;
    protected final int digestSizeBytes;
    protected final int pqSecurityLevel;
    protected final Xof digest;
    private final int transform;
    private final int parameters;
    private int signatureLength;
    protected final LowmcConstants lowmcConstants;
    
    public int getSecretKeySize() {
        return this.CRYPTO_SECRETKEYBYTES;
    }
    
    public int getPublicKeySize() {
        return this.CRYPTO_PUBLICKEYBYTES;
    }
    
    public int getSignatureSize(final int n) {
        return this.CRYPTO_BYTES + n;
    }
    
    public int getTrueSignatureSize() {
        return this.signatureLength;
    }
    
    PicnicEngine(final int parameters, final LowmcConstants lowmcConstants) {
        this.lowmcConstants = lowmcConstants;
        switch (this.parameters = parameters) {
            case 1:
            case 2: {
                this.pqSecurityLevel = 64;
                this.stateSizeBits = 128;
                this.numMPCRounds = 219;
                this.numMPCParties = 3;
                this.numSboxes = 10;
                this.numRounds = 20;
                this.digestSizeBytes = 32;
                this.numOpenedRounds = 0;
                break;
            }
            case 3:
            case 4: {
                this.pqSecurityLevel = 96;
                this.stateSizeBits = 192;
                this.numMPCRounds = 329;
                this.numMPCParties = 3;
                this.numSboxes = 10;
                this.numRounds = 30;
                this.digestSizeBytes = 48;
                this.numOpenedRounds = 0;
                break;
            }
            case 5:
            case 6: {
                this.pqSecurityLevel = 128;
                this.stateSizeBits = 256;
                this.numMPCRounds = 438;
                this.numMPCParties = 3;
                this.numSboxes = 10;
                this.numRounds = 38;
                this.digestSizeBytes = 64;
                this.numOpenedRounds = 0;
                break;
            }
            case 7: {
                this.pqSecurityLevel = 64;
                this.stateSizeBits = 129;
                this.numMPCRounds = 250;
                this.numOpenedRounds = 36;
                this.numMPCParties = 16;
                this.numSboxes = 43;
                this.numRounds = 4;
                this.digestSizeBytes = 32;
                break;
            }
            case 8: {
                this.pqSecurityLevel = 96;
                this.stateSizeBits = 192;
                this.numMPCRounds = 419;
                this.numOpenedRounds = 52;
                this.numMPCParties = 16;
                this.numSboxes = 64;
                this.numRounds = 4;
                this.digestSizeBytes = 48;
                break;
            }
            case 9: {
                this.pqSecurityLevel = 128;
                this.stateSizeBits = 255;
                this.numMPCRounds = 601;
                this.numOpenedRounds = 68;
                this.numMPCParties = 16;
                this.numSboxes = 85;
                this.numRounds = 4;
                this.digestSizeBytes = 64;
                break;
            }
            case 10: {
                this.pqSecurityLevel = 64;
                this.stateSizeBits = 129;
                this.numMPCRounds = 219;
                this.numMPCParties = 3;
                this.numSboxes = 43;
                this.numRounds = 4;
                this.digestSizeBytes = 32;
                this.numOpenedRounds = 0;
                break;
            }
            case 11: {
                this.pqSecurityLevel = 96;
                this.stateSizeBits = 192;
                this.numMPCRounds = 329;
                this.numMPCParties = 3;
                this.numSboxes = 64;
                this.numRounds = 4;
                this.digestSizeBytes = 48;
                this.numOpenedRounds = 0;
                break;
            }
            case 12: {
                this.pqSecurityLevel = 128;
                this.stateSizeBits = 255;
                this.numMPCRounds = 438;
                this.numMPCParties = 3;
                this.numSboxes = 85;
                this.numRounds = 4;
                this.digestSizeBytes = 64;
                this.numOpenedRounds = 0;
                break;
            }
            default: {
                throw new IllegalArgumentException("unknown parameter set " + this.parameters);
            }
        }
        switch (this.parameters) {
            case 1: {
                this.CRYPTO_SECRETKEYBYTES = 49;
                this.CRYPTO_PUBLICKEYBYTES = 33;
                this.CRYPTO_BYTES = 34036;
                break;
            }
            case 2: {
                this.CRYPTO_SECRETKEYBYTES = 49;
                this.CRYPTO_PUBLICKEYBYTES = 33;
                this.CRYPTO_BYTES = 53965;
                break;
            }
            case 3: {
                this.CRYPTO_SECRETKEYBYTES = 73;
                this.CRYPTO_PUBLICKEYBYTES = 49;
                this.CRYPTO_BYTES = 76784;
                break;
            }
            case 4: {
                this.CRYPTO_SECRETKEYBYTES = 73;
                this.CRYPTO_PUBLICKEYBYTES = 49;
                this.CRYPTO_BYTES = 121857;
                break;
            }
            case 5: {
                this.CRYPTO_SECRETKEYBYTES = 97;
                this.CRYPTO_PUBLICKEYBYTES = 65;
                this.CRYPTO_BYTES = 132876;
                break;
            }
            case 6: {
                this.CRYPTO_SECRETKEYBYTES = 97;
                this.CRYPTO_PUBLICKEYBYTES = 65;
                this.CRYPTO_BYTES = 209526;
                break;
            }
            case 7: {
                this.CRYPTO_SECRETKEYBYTES = 52;
                this.CRYPTO_PUBLICKEYBYTES = 35;
                this.CRYPTO_BYTES = 14612;
                break;
            }
            case 8: {
                this.CRYPTO_SECRETKEYBYTES = 73;
                this.CRYPTO_PUBLICKEYBYTES = 49;
                this.CRYPTO_BYTES = 35028;
                break;
            }
            case 9: {
                this.CRYPTO_SECRETKEYBYTES = 97;
                this.CRYPTO_PUBLICKEYBYTES = 65;
                this.CRYPTO_BYTES = 61028;
                break;
            }
            case 10: {
                this.CRYPTO_SECRETKEYBYTES = 52;
                this.CRYPTO_PUBLICKEYBYTES = 35;
                this.CRYPTO_BYTES = 32061;
                break;
            }
            case 11: {
                this.CRYPTO_SECRETKEYBYTES = 73;
                this.CRYPTO_PUBLICKEYBYTES = 49;
                this.CRYPTO_BYTES = 71179;
                break;
            }
            case 12: {
                this.CRYPTO_SECRETKEYBYTES = 97;
                this.CRYPTO_PUBLICKEYBYTES = 65;
                this.CRYPTO_BYTES = 126286;
                break;
            }
            default: {
                this.CRYPTO_SECRETKEYBYTES = -1;
                this.CRYPTO_PUBLICKEYBYTES = -1;
                this.CRYPTO_BYTES = -1;
                break;
            }
        }
        this.andSizeBytes = Utils.numBytes(this.numSboxes * 3 * this.numRounds);
        this.stateSizeBytes = Utils.numBytes(this.stateSizeBits);
        this.seedSizeBytes = Utils.numBytes(2 * this.pqSecurityLevel);
        this.stateSizeWords = (this.stateSizeBits + 32 - 1) / 32;
        switch (this.parameters) {
            case 1:
            case 3:
            case 5:
            case 7:
            case 8:
            case 9:
            case 10:
            case 11:
            case 12: {
                this.transform = 0;
                break;
            }
            case 2:
            case 4:
            case 6: {
                this.transform = 1;
                break;
            }
            default: {
                this.transform = 255;
                break;
            }
        }
        if (this.transform == 1) {
            this.UnruhGWithoutInputBytes = this.seedSizeBytes + this.andSizeBytes;
            this.UnruhGWithInputBytes = this.UnruhGWithoutInputBytes + this.stateSizeBytes;
        }
        else {
            this.UnruhGWithoutInputBytes = 0;
            this.UnruhGWithInputBytes = 0;
        }
        if (this.stateSizeBits == 128 || this.stateSizeBits == 129) {
            this.digest = new SHAKEDigest(128);
        }
        else {
            this.digest = new SHAKEDigest(256);
        }
    }
    
    public boolean crypto_sign_open(final byte[] array, final byte[] array2, final byte[] array3) {
        if (this.picnic_verify(array3, Arrays.copyOfRange(array2, 4, 4 + array.length), array2, Pack.littleEndianToInt(array2, 0)) == -1) {
            return false;
        }
        System.arraycopy(array2, 4, array, 0, array.length);
        return true;
    }
    
    private int picnic_verify(final byte[] array, final byte[] array2, final byte[] array3, final int n) {
        final int[] array4 = new int[this.stateSizeWords];
        final int[] array5 = new int[this.stateSizeWords];
        this.picnic_read_public_key(array4, array5, array);
        if (is_picnic3(this.parameters)) {
            final Signature2 signature2 = new Signature2(this);
            if (this.deserializeSignature2(signature2, array3, n, array2.length + 4) != 0) {
                PicnicEngine.LOG.fine("Error couldn't deserialize signature (2)!");
                return -1;
            }
            return this.verify_picnic3(signature2, array4, array5, array2);
        }
        else {
            final Signature signature3 = new Signature(this);
            if (this.deserializeSignature(signature3, array3, n, array2.length + 4) != 0) {
                PicnicEngine.LOG.fine("Error couldn't deserialize signature!");
                return -1;
            }
            return this.verify(signature3, array4, array5, array2);
        }
    }
    
    private int verify(final Signature signature, final int[] array, final int[] array2, final byte[] array3) {
        final byte[][][] array4 = new byte[this.numMPCRounds][this.numMPCParties][this.digestSizeBytes];
        final byte[][][] array5 = new byte[this.numMPCRounds][3][this.UnruhGWithInputBytes];
        final int[][][] array6 = new int[this.numMPCRounds][3][this.stateSizeBytes];
        final Signature.Proof[] proofs = signature.proofs;
        final byte[] challengeBits = signature.challengeBits;
        int n = 0;
        final byte[] array7 = new byte[Math.max(6 * this.stateSizeBytes, this.stateSizeBytes + this.andSizeBytes)];
        final Tape tape = new Tape(this);
        final View[] array8 = new View[this.numMPCRounds];
        final View[] array9 = new View[this.numMPCRounds];
        for (int i = 0; i < this.numMPCRounds; ++i) {
            array8[i] = new View(this);
            array9[i] = new View(this);
            if (!this.verifyProof(proofs[i], array8[i], array9[i], this.getChallenge(challengeBits, i), signature.salt, i, array7, array2, tape)) {
                PicnicEngine.LOG.fine("Invalid signature. Did not verify");
                return -1;
            }
            final int challenge = this.getChallenge(challengeBits, i);
            this.Commit(proofs[i].seed1, 0, array8[i], array4[i][challenge]);
            this.Commit(proofs[i].seed2, 0, array9[i], array4[i][(challenge + 1) % 3]);
            System.arraycopy(proofs[i].view3Commitment, 0, array4[i][(challenge + 2) % 3], 0, this.digestSizeBytes);
            if (this.transform == 1) {
                this.G(challenge, proofs[i].seed1, 0, array8[i], array5[i][challenge]);
                this.G((challenge + 1) % 3, proofs[i].seed2, 0, array9[i], array5[i][(challenge + 1) % 3]);
                System.arraycopy(proofs[i].view3UnruhG, 0, array5[i][(challenge + 2) % 3], 0, (challenge == 0) ? this.UnruhGWithInputBytes : this.UnruhGWithoutInputBytes);
            }
            array6[i][challenge] = array8[i].outputShare;
            array6[i][(challenge + 1) % 3] = array9[i].outputShare;
            final int[] array10 = new int[this.stateSizeWords];
            this.xor_three(array10, array8[i].outputShare, array9[i].outputShare, array);
            array6[i][(challenge + 2) % 3] = array10;
        }
        final byte[] array11 = new byte[Utils.numBytes(2 * this.numMPCRounds)];
        this.H3(array, array2, array6, array4, array11, signature.salt, array3, array5);
        if (!subarrayEquals(challengeBits, array11, Utils.numBytes(2 * this.numMPCRounds))) {
            PicnicEngine.LOG.fine("Invalid signature. Did not verify");
            n = -1;
        }
        return n;
    }
    
    boolean verifyProof(final Signature.Proof proof, final View view, final View view2, final int n, final byte[] array, final int n2, final byte[] array2, final int[] array3, final Tape tape) {
        System.arraycopy(proof.communicatedBits, 0, view2.communicatedBits, 0, this.andSizeBytes);
        tape.pos = 0;
        boolean b = false;
        switch (n) {
            case 0: {
                final boolean randomTape = this.createRandomTape(proof.seed1, 0, array, n2, 0, array2, this.stateSizeBytes + this.andSizeBytes);
                Pack.littleEndianToInt(array2, 0, view.inputShare);
                System.arraycopy(array2, this.stateSizeBytes, tape.tapes[0], 0, this.andSizeBytes);
                b = (randomTape && this.createRandomTape(proof.seed2, 0, array, n2, 1, array2, this.stateSizeBytes + this.andSizeBytes));
                if (!b) {
                    break;
                }
                Pack.littleEndianToInt(array2, 0, view2.inputShare);
                System.arraycopy(array2, this.stateSizeBytes, tape.tapes[1], 0, this.andSizeBytes);
                break;
            }
            case 1: {
                final boolean randomTape2 = this.createRandomTape(proof.seed1, 0, array, n2, 1, array2, this.stateSizeBytes + this.andSizeBytes);
                Pack.littleEndianToInt(array2, 0, view.inputShare);
                System.arraycopy(array2, this.stateSizeBytes, tape.tapes[0], 0, this.andSizeBytes);
                b = (randomTape2 && this.createRandomTape(proof.seed2, 0, array, n2, 2, tape.tapes[1], this.andSizeBytes));
                if (!b) {
                    break;
                }
                System.arraycopy(proof.inputShare, 0, view2.inputShare, 0, this.stateSizeWords);
                break;
            }
            case 2: {
                final boolean randomTape3 = this.createRandomTape(proof.seed1, 0, array, n2, 2, tape.tapes[0], this.andSizeBytes);
                System.arraycopy(proof.inputShare, 0, view.inputShare, 0, this.stateSizeWords);
                b = (randomTape3 && this.createRandomTape(proof.seed2, 0, array, n2, 0, array2, this.stateSizeBytes + this.andSizeBytes));
                if (!b) {
                    break;
                }
                Pack.littleEndianToInt(array2, 0, view2.inputShare);
                System.arraycopy(array2, this.stateSizeBytes, tape.tapes[1], 0, this.andSizeBytes);
                break;
            }
            default: {
                PicnicEngine.LOG.fine("Invalid Challenge!");
                break;
            }
        }
        if (!b) {
            PicnicEngine.LOG.fine("Failed to generate random tapes, signature verification will fail (but signature may actually be valid)");
            return false;
        }
        Utils.zeroTrailingBits(view.inputShare, this.stateSizeBits);
        Utils.zeroTrailingBits(view2.inputShare, this.stateSizeBits);
        this.mpc_LowMC_verify(view, view2, tape, Pack.littleEndianToInt(array2, 0, array2.length / 4), array3, n);
        return true;
    }
    
    void mpc_LowMC_verify(final View view, final View view2, final Tape tape, final int[] array, final int[] array2, final int n) {
        Arrays.fill(array, 0, array.length, 0);
        this.mpc_xor_constant_verify(array, array2, 0, this.stateSizeWords, n);
        final KMatricesWithPointer kMatrix = this.lowmcConstants.KMatrix(this, 0);
        this.matrix_mul_offset(array, 0, view.inputShare, 0, kMatrix.getData(), kMatrix.getMatrixPointer());
        this.matrix_mul_offset(array, this.stateSizeWords, view2.inputShare, 0, kMatrix.getData(), kMatrix.getMatrixPointer());
        this.mpc_xor(array, array, 2);
        for (int i = 1; i <= this.numRounds; ++i) {
            final KMatricesWithPointer kMatrix2 = this.lowmcConstants.KMatrix(this, i);
            this.matrix_mul_offset(array, 0, view.inputShare, 0, kMatrix2.getData(), kMatrix2.getMatrixPointer());
            this.matrix_mul_offset(array, this.stateSizeWords, view2.inputShare, 0, kMatrix2.getData(), kMatrix2.getMatrixPointer());
            this.mpc_substitution_verify(array, tape, view, view2);
            final KMatricesWithPointer lMatrix = this.lowmcConstants.LMatrix(this, i - 1);
            this.mpc_matrix_mul(array, 2 * this.stateSizeWords, array, 2 * this.stateSizeWords, lMatrix.getData(), lMatrix.getMatrixPointer(), 2);
            final KMatricesWithPointer rConstant = this.lowmcConstants.RConstant(this, i - 1);
            this.mpc_xor_constant_verify(array, rConstant.getData(), rConstant.getMatrixPointer(), this.stateSizeWords, n);
            this.mpc_xor(array, array, 2);
        }
        System.arraycopy(array, 2 * this.stateSizeWords, view.outputShare, 0, this.stateSizeWords);
        System.arraycopy(array, 3 * this.stateSizeWords, view2.outputShare, 0, this.stateSizeWords);
    }
    
    void mpc_substitution_verify(final int[] array, final Tape tape, final View view, final View view2) {
        final int[] array2 = new int[2];
        final int[] array3 = new int[2];
        final int[] array4 = new int[2];
        final int[] array5 = new int[2];
        final int[] array6 = new int[2];
        final int[] array7 = new int[2];
        for (int i = 0; i < this.numSboxes * 3; i += 3) {
            for (int j = 0; j < 2; ++j) {
                final int n = (2 + j) * this.stateSizeWords * 32;
                array2[j] = Utils.getBitFromWordArray(array, n + i + 2);
                array3[j] = Utils.getBitFromWordArray(array, n + i + 1);
                array4[j] = Utils.getBitFromWordArray(array, n + i);
            }
            this.mpc_AND_verify(array2, array3, array5, tape, view, view2);
            this.mpc_AND_verify(array3, array4, array6, tape, view, view2);
            this.mpc_AND_verify(array4, array2, array7, tape, view, view2);
            for (int k = 0; k < 2; ++k) {
                final int n2 = (2 + k) * this.stateSizeWords * 32;
                Utils.setBitInWordArray(array, n2 + i + 2, array2[k] ^ array6[k]);
                Utils.setBitInWordArray(array, n2 + i + 1, array2[k] ^ array3[k] ^ array7[k]);
                Utils.setBitInWordArray(array, n2 + i, array2[k] ^ array3[k] ^ array4[k] ^ array5[k]);
            }
        }
    }
    
    void mpc_AND_verify(final int[] array, final int[] array2, final int[] array3, final Tape tape, final View view, final View view2) {
        final byte bit = Utils.getBit(tape.tapes[0], tape.pos);
        final byte bit2 = Utils.getBit(tape.tapes[1], tape.pos);
        final int n = array[0];
        final int n2 = array[1];
        final int n3 = array2[0];
        array3[0] = ((n & array2[1]) ^ (n2 & n3) ^ (n & n3) ^ bit ^ bit2);
        Utils.setBit(view.communicatedBits, tape.pos, (byte)array3[0]);
        array3[1] = Utils.getBit(view2.communicatedBits, tape.pos);
        ++tape.pos;
    }
    
    private void mpc_xor_constant_verify(final int[] array, final int[] array2, final int n, final int n2, final int n3) {
        int n4;
        if (n3 == 0) {
            n4 = 2 * this.stateSizeWords;
        }
        else {
            if (n3 != 2) {
                return;
            }
            n4 = 3 * this.stateSizeWords;
        }
        for (int i = 0; i < n2; ++i) {
            final int n5 = i + n4;
            array[n5] ^= array2[i + n];
        }
    }
    
    private int deserializeSignature(final Signature signature, final byte[] array, final int i, int n) {
        final Signature.Proof[] proofs = signature.proofs;
        final byte[] challengeBits = signature.challengeBits;
        final int numBytes = Utils.numBytes(2 * this.numMPCRounds);
        if (i < numBytes) {
            return -1;
        }
        final int countNonZeroChallenges = this.countNonZeroChallenges(array, n);
        if (countNonZeroChallenges < 0) {
            return -1;
        }
        int j = numBytes + 32 + this.numMPCRounds * (2 * this.seedSizeBytes + this.andSizeBytes + this.digestSizeBytes) + countNonZeroChallenges * this.stateSizeBytes;
        if (this.transform == 1) {
            j = j + this.UnruhGWithInputBytes * (this.numMPCRounds - countNonZeroChallenges) + this.UnruhGWithoutInputBytes * countNonZeroChallenges;
        }
        if (i != j) {
            PicnicEngine.LOG.fine("sigBytesLen = " + i + ", expected bytesRequired = " + j);
            return -1;
        }
        System.arraycopy(array, n, challengeBits, 0, numBytes);
        n += numBytes;
        System.arraycopy(array, n, signature.salt, 0, 32);
        n += 32;
        for (int k = 0; k < this.numMPCRounds; ++k) {
            final int challenge = this.getChallenge(challengeBits, k);
            System.arraycopy(array, n, proofs[k].view3Commitment, 0, this.digestSizeBytes);
            n += this.digestSizeBytes;
            if (this.transform == 1) {
                final int n2 = (challenge == 0) ? this.UnruhGWithInputBytes : this.UnruhGWithoutInputBytes;
                System.arraycopy(array, n, proofs[k].view3UnruhG, 0, n2);
                n += n2;
            }
            System.arraycopy(array, n, proofs[k].communicatedBits, 0, this.andSizeBytes);
            n += this.andSizeBytes;
            System.arraycopy(array, n, proofs[k].seed1, 0, this.seedSizeBytes);
            n += this.seedSizeBytes;
            System.arraycopy(array, n, proofs[k].seed2, 0, this.seedSizeBytes);
            n += this.seedSizeBytes;
            if (challenge == 1 || challenge == 2) {
                Pack.littleEndianToInt(array, n, proofs[k].inputShare, 0, this.stateSizeBytes / 4);
                if (this.stateSizeBits == 129) {
                    proofs[k].inputShare[this.stateSizeWords - 1] = (array[n + this.stateSizeBytes - 1] & 0xFF);
                }
                n += this.stateSizeBytes;
                if (!this.arePaddingBitsZero(proofs[k].inputShare, this.stateSizeBits)) {
                    return -1;
                }
            }
        }
        return 0;
    }
    
    private int countNonZeroChallenges(final byte[] array, final int n) {
        int n2 = 0;
        int n3 = 0;
        int n4;
        for (n4 = 0; n4 + 16 <= this.numMPCRounds; n4 += 16) {
            final int littleEndianToInt = Pack.littleEndianToInt(array, n + (n4 >>> 2));
            n3 |= (littleEndianToInt & littleEndianToInt >>> 1);
            n2 += Integers.bitCount((littleEndianToInt ^ littleEndianToInt >>> 1) & 0x55555555);
        }
        final int n5 = (this.numMPCRounds - n4) * 2;
        if (n5 > 0) {
            final int n6 = Pack.littleEndianToInt_Low(array, n + (n4 >>> 2), (n5 + 7) / 8) & Utils.getTrailingBitsMask(n5);
            n3 |= (n6 & n6 >>> 1);
            n2 += Integers.bitCount((n6 ^ n6 >>> 1) & 0x55555555);
        }
        return ((n3 & 0x55555555) == 0x0) ? n2 : -1;
    }
    
    private void picnic_read_public_key(final int[] array, final int[] array2, final byte[] array3) {
        final int n = 1;
        final int n2 = 1 + this.stateSizeBytes;
        final int n3 = this.stateSizeBytes / 4;
        Pack.littleEndianToInt(array3, n, array, 0, n3);
        Pack.littleEndianToInt(array3, n2, array2, 0, n3);
        if (n3 < this.stateSizeWords) {
            final int n4 = n3 * 4;
            final int n5 = this.stateSizeBytes - n4;
            array[n3] = Pack.littleEndianToInt_Low(array3, n + n4, n5);
            array2[n3] = Pack.littleEndianToInt_Low(array3, n2 + n4, n5);
        }
    }
    
    private int verify_picnic3(final Signature2 signature2, final int[] array, final int[] array2, final byte[] array3) {
        final byte[][][] array4 = new byte[this.numMPCRounds][this.numMPCParties][this.digestSizeBytes];
        final byte[][] array5 = new byte[this.numMPCRounds][this.digestSizeBytes];
        final byte[][] array6 = new byte[this.numMPCRounds][this.digestSizeBytes];
        final Msg[] array7 = new Msg[this.numMPCRounds];
        final Tree tree = new Tree(this, this.numMPCRounds, this.digestSizeBytes);
        final byte[] array8 = new byte[64];
        final Tree[] array9 = new Tree[this.numMPCRounds];
        final Tape[] array10 = new Tape[this.numMPCRounds];
        final Tree tree2 = new Tree(this, this.numMPCRounds, this.seedSizeBytes);
        if (tree2.reconstructSeeds(signature2.challengeC, this.numOpenedRounds, signature2.iSeedInfo, signature2.iSeedInfoLen, signature2.salt, 0) != 0) {
            return -1;
        }
        for (int i = 0; i < this.numMPCRounds; ++i) {
            if (!this.contains(signature2.challengeC, this.numOpenedRounds, i)) {
                (array9[i] = new Tree(this, this.numMPCParties, this.seedSizeBytes)).generateSeeds(tree2.getLeaf(i), signature2.salt, i);
            }
            else {
                array9[i] = new Tree(this, this.numMPCParties, this.seedSizeBytes);
                if (array9[i].reconstructSeeds(new int[] { signature2.challengeP[indexOf(signature2.challengeC, this.numOpenedRounds, i)] }, 1, signature2.proofs[i].seedInfo, signature2.proofs[i].seedInfoLen, signature2.salt, i) != 0) {
                    PicnicEngine.LOG.fine("Failed to reconstruct seeds for round " + i);
                    return -1;
                }
            }
        }
        final int n = this.numMPCParties - 1;
        final byte[] array11 = new byte[176];
        for (int j = 0; j < this.numMPCRounds; ++j) {
            this.createRandomTapes(array10[j] = new Tape(this), array9[j].getLeaves(), array9[j].getLeavesOffset(), signature2.salt, j);
            if (!this.contains(signature2.challengeC, this.numOpenedRounds, j)) {
                array10[j].computeAuxTape(null);
                for (int k = 0; k < n; ++k) {
                    this.commit(array4[j][k], array9[j].getLeaf(k), null, signature2.salt, j, k);
                }
                this.getAuxBits(array11, array10[j]);
                this.commit(array4[j][n], array9[j].getLeaf(n), array11, signature2.salt, j, n);
            }
            else {
                final int n2 = signature2.challengeP[indexOf(signature2.challengeC, this.numOpenedRounds, j)];
                for (int l = 0; l < n; ++l) {
                    if (l != n2) {
                        this.commit(array4[j][l], array9[j].getLeaf(l), null, signature2.salt, j, l);
                    }
                }
                if (n != n2) {
                    this.commit(array4[j][n], array9[j].getLeaf(n), signature2.proofs[j].aux, signature2.salt, j, n);
                }
                System.arraycopy(signature2.proofs[j].C, 0, array4[j][n2], 0, this.digestSizeBytes);
            }
        }
        for (int n3 = 0; n3 < this.numMPCRounds; ++n3) {
            this.commit_h(array5[n3], array4[n3]);
        }
        final int[] array12 = new int[this.stateSizeBits];
        for (int m = 0; m < this.numMPCRounds; ++m) {
            array7[m] = new Msg(this);
            if (this.contains(signature2.challengeC, this.numOpenedRounds, m)) {
                final int unopened = signature2.challengeP[indexOf(signature2.challengeC, this.numOpenedRounds, m)];
                if (unopened != n) {
                    array10[m].setAuxBits(signature2.proofs[m].aux);
                }
                System.arraycopy(signature2.proofs[m].msgs, 0, array7[m].msgs[unopened], 0, this.andSizeBytes);
                Arrays.fill(array10[m].tapes[unopened], (byte)0);
                array7[m].unopened = unopened;
                final byte[] array13 = new byte[this.stateSizeWords * 4];
                System.arraycopy(signature2.proofs[m].input, 0, array13, 0, signature2.proofs[m].input.length);
                final int[] array14 = new int[this.stateSizeWords];
                Pack.littleEndianToInt(array13, 0, array14, 0, this.stateSizeWords);
                if (this.simulateOnline(array14, array10[m], array12, array7[m], array2, array) != 0) {
                    PicnicEngine.LOG.fine("MPC simulation failed for round " + m + ", signature invalid");
                    return -1;
                }
                this.commit_v(array6[m], signature2.proofs[m].input, array7[m]);
            }
            else {
                array6[m] = null;
            }
        }
        if (tree.addMerkleNodes(this.getMissingLeavesList(signature2.challengeC), this.numMPCRounds - this.numOpenedRounds, signature2.cvInfo, signature2.cvInfoLen) != 0) {
            return -1;
        }
        final int verifyMerkleTree = tree.verifyMerkleTree(array6, signature2.salt);
        if (verifyMerkleTree != 0) {
            return -1;
        }
        this.HCP(array8, null, null, array5, tree.nodes[0], signature2.salt, array, array2, array3);
        if (!subarrayEquals(signature2.challengeHash, array8, this.digestSizeBytes)) {
            PicnicEngine.LOG.fine("Challenge does not match, signature invalid");
            return -1;
        }
        return verifyMerkleTree;
    }
    
    private int deserializeSignature2(final Signature2 signature2, final byte[] array, final int i, int n) {
        final int n2 = this.digestSizeBytes + 32;
        if (array.length < n2) {
            return -1;
        }
        System.arraycopy(array, n, signature2.challengeHash, 0, this.digestSizeBytes);
        n += this.digestSizeBytes;
        System.arraycopy(array, n, signature2.salt, 0, 32);
        n += 32;
        this.expandChallengeHash(signature2.challengeHash, signature2.challengeC, signature2.challengeP);
        signature2.iSeedInfoLen = new Tree(this, this.numMPCRounds, this.seedSizeBytes).revealSeedsSize(signature2.challengeC, this.numOpenedRounds);
        final int n3 = n2 + signature2.iSeedInfoLen;
        signature2.cvInfoLen = new Tree(this, this.numMPCRounds, this.digestSizeBytes).openMerkleTreeSize(this.getMissingLeavesList(signature2.challengeC), this.numMPCRounds - this.numOpenedRounds);
        int j = n3 + signature2.cvInfoLen;
        final int revealSeedsSize = new Tree(this, this.numMPCParties, this.seedSizeBytes).revealSeedsSize(new int[1], 1);
        for (int k = 0; k < this.numMPCRounds; ++k) {
            if (this.contains(signature2.challengeC, this.numOpenedRounds, k)) {
                if (signature2.challengeP[indexOf(signature2.challengeC, this.numOpenedRounds, k)] != this.numMPCParties - 1) {
                    j += this.andSizeBytes;
                }
                j = j + revealSeedsSize + this.stateSizeBytes + this.andSizeBytes + this.digestSizeBytes;
            }
        }
        if (i != j) {
            PicnicEngine.LOG.fine("sigLen = " + i + ", expected bytesRequired = " + j);
            return -1;
        }
        System.arraycopy(array, n, signature2.iSeedInfo = new byte[signature2.iSeedInfoLen], 0, signature2.iSeedInfoLen);
        n += signature2.iSeedInfoLen;
        System.arraycopy(array, n, signature2.cvInfo = new byte[signature2.cvInfoLen], 0, signature2.cvInfoLen);
        n += signature2.cvInfoLen;
        for (int l = 0; l < this.numMPCRounds; ++l) {
            if (this.contains(signature2.challengeC, this.numOpenedRounds, l)) {
                signature2.proofs[l] = new Signature2.Proof2(this);
                signature2.proofs[l].seedInfoLen = revealSeedsSize;
                System.arraycopy(array, n, signature2.proofs[l].seedInfo = new byte[signature2.proofs[l].seedInfoLen], 0, signature2.proofs[l].seedInfoLen);
                n += signature2.proofs[l].seedInfoLen;
                if (signature2.challengeP[indexOf(signature2.challengeC, this.numOpenedRounds, l)] != this.numMPCParties - 1) {
                    System.arraycopy(array, n, signature2.proofs[l].aux, 0, this.andSizeBytes);
                    n += this.andSizeBytes;
                    if (!this.arePaddingBitsZero(signature2.proofs[l].aux, 3 * this.numRounds * this.numSboxes)) {
                        PicnicEngine.LOG.fine("failed while deserializing aux bits");
                        return -1;
                    }
                }
                System.arraycopy(array, n, signature2.proofs[l].input, 0, this.stateSizeBytes);
                n += this.stateSizeBytes;
                final int andSizeBytes = this.andSizeBytes;
                System.arraycopy(array, n, signature2.proofs[l].msgs, 0, andSizeBytes);
                n += andSizeBytes;
                if (!this.arePaddingBitsZero(signature2.proofs[l].msgs, 3 * this.numRounds * this.numSboxes)) {
                    PicnicEngine.LOG.fine("failed while deserializing msgs bits");
                    return -1;
                }
                System.arraycopy(array, n, signature2.proofs[l].C, 0, this.digestSizeBytes);
                n += this.digestSizeBytes;
            }
        }
        return 0;
    }
    
    private boolean arePaddingBitsZero(final byte[] array, final int n) {
        for (int numBytes = Utils.numBytes(n), i = n; i < numBytes * 8; ++i) {
            if (Utils.getBit(array, i) != 0) {
                return false;
            }
        }
        return true;
    }
    
    private boolean arePaddingBitsZero(final int[] array, final int n) {
        return (n & 0x1F) == 0x0 || (array[n >>> 5] & ~Utils.getTrailingBitsMask(n)) == 0x0;
    }
    
    public void crypto_sign(final byte[] array, final byte[] array2, final byte[] array3) {
        if (!this.picnic_sign(array3, array2, array)) {
            return;
        }
        System.arraycopy(array2, 0, array, 4, array2.length);
    }
    
    private boolean picnic_sign(final byte[] array, final byte[] array2, final byte[] array3) {
        final int[] array4 = new int[this.stateSizeWords];
        final int[] array5 = new int[this.stateSizeWords];
        final int[] array6 = new int[this.stateSizeWords];
        final int n = 1;
        final int n2 = 1 + this.stateSizeBytes;
        final int n3 = 1 + 2 * this.stateSizeBytes;
        final int n4 = this.stateSizeBytes / 4;
        Pack.littleEndianToInt(array, n, array4, 0, n4);
        Pack.littleEndianToInt(array, n2, array5, 0, n4);
        Pack.littleEndianToInt(array, n3, array6, 0, n4);
        if (n4 < this.stateSizeWords) {
            final int n5 = n4 * 4;
            final int n6 = this.stateSizeBytes - n5;
            array4[n4] = Pack.littleEndianToInt_Low(array, n + n5, n6);
            array5[n4] = Pack.littleEndianToInt_Low(array, n2 + n5, n6);
            array6[n4] = Pack.littleEndianToInt_Low(array, n3 + n5, n6);
        }
        if (!is_picnic3(this.parameters)) {
            final Signature signature = new Signature(this);
            if (this.sign_picnic1(array4, array5, array6, array2, signature) != 0) {
                PicnicEngine.LOG.fine("Failed to create signature");
                return false;
            }
            final int serializeSignature = this.serializeSignature(signature, array3, array2.length + 4);
            if (serializeSignature < 0) {
                PicnicEngine.LOG.fine("Failed to serialize signature");
                return false;
            }
            Pack.intToLittleEndian(this.signatureLength = serializeSignature, array3, 0);
            return true;
        }
        else {
            final Signature2 signature2 = new Signature2(this);
            if (!this.sign_picnic3(array4, array5, array6, array2, signature2)) {
                PicnicEngine.LOG.fine("Failed to create signature");
                return false;
            }
            final int serializeSignature2 = this.serializeSignature2(signature2, array3, array2.length + 4);
            if (serializeSignature2 < 0) {
                PicnicEngine.LOG.fine("Failed to serialize signature");
                return false;
            }
            Pack.intToLittleEndian(this.signatureLength = serializeSignature2, array3, 0);
            return true;
        }
    }
    
    int serializeSignature(final Signature signature, final byte[] array, final int n) {
        final Signature.Proof[] proofs = signature.proofs;
        final byte[] challengeBits = signature.challengeBits;
        int n2 = Utils.numBytes(2 * this.numMPCRounds) + 32 + this.numMPCRounds * (2 * this.seedSizeBytes + this.stateSizeBytes + this.andSizeBytes + this.digestSizeBytes);
        if (this.transform == 1) {
            n2 += this.UnruhGWithoutInputBytes * this.numMPCRounds;
        }
        if (this.CRYPTO_BYTES < n2) {
            return -1;
        }
        System.arraycopy(challengeBits, 0, array, n, Utils.numBytes(2 * this.numMPCRounds));
        int n3 = n + Utils.numBytes(2 * this.numMPCRounds);
        System.arraycopy(signature.salt, 0, array, n3, 32);
        n3 += 32;
        for (int i = 0; i < this.numMPCRounds; ++i) {
            final int challenge = this.getChallenge(challengeBits, i);
            System.arraycopy(proofs[i].view3Commitment, 0, array, n3, this.digestSizeBytes);
            int n4 = n3 + this.digestSizeBytes;
            if (this.transform == 1) {
                final int n5 = (challenge == 0) ? this.UnruhGWithInputBytes : this.UnruhGWithoutInputBytes;
                System.arraycopy(proofs[i].view3UnruhG, 0, array, n4, n5);
                n4 += n5;
            }
            System.arraycopy(proofs[i].communicatedBits, 0, array, n4, this.andSizeBytes);
            final int n6 = n4 + this.andSizeBytes;
            System.arraycopy(proofs[i].seed1, 0, array, n6, this.seedSizeBytes);
            final int n7 = n6 + this.seedSizeBytes;
            System.arraycopy(proofs[i].seed2, 0, array, n7, this.seedSizeBytes);
            n3 = n7 + this.seedSizeBytes;
            if (challenge == 1 || challenge == 2) {
                Pack.intToLittleEndian(proofs[i].inputShare, 0, this.stateSizeWords, array, n3);
                n3 += this.stateSizeBytes;
            }
        }
        return n3 - n;
    }
    
    int getChallenge(final byte[] array, final int n) {
        return Utils.getCrumbAligned(array, n);
    }
    
    private int serializeSignature2(final Signature2 signature2, final byte[] array, final int n) {
        int n2 = this.digestSizeBytes + 32 + signature2.iSeedInfoLen + signature2.cvInfoLen;
        for (int i = 0; i < this.numMPCRounds; ++i) {
            if (this.contains(signature2.challengeC, this.numOpenedRounds, i)) {
                final int n3 = signature2.challengeP[indexOf(signature2.challengeC, this.numOpenedRounds, i)];
                int n4 = n2 + signature2.proofs[i].seedInfoLen;
                if (n3 != this.numMPCParties - 1) {
                    n4 += this.andSizeBytes;
                }
                n2 = n4 + this.stateSizeBytes + this.andSizeBytes + this.digestSizeBytes;
            }
        }
        if (array.length < n2) {
            return -1;
        }
        System.arraycopy(signature2.challengeHash, 0, array, n, this.digestSizeBytes);
        int n5 = n + this.digestSizeBytes;
        System.arraycopy(signature2.salt, 0, array, n5, 32);
        n5 += 32;
        System.arraycopy(signature2.iSeedInfo, 0, array, n5, signature2.iSeedInfoLen);
        final int n6 = n5 + signature2.iSeedInfoLen;
        System.arraycopy(signature2.cvInfo, 0, array, n6, signature2.cvInfoLen);
        int n7 = n6 + signature2.cvInfoLen;
        for (int j = 0; j < this.numMPCRounds; ++j) {
            if (this.contains(signature2.challengeC, this.numOpenedRounds, j)) {
                System.arraycopy(signature2.proofs[j].seedInfo, 0, array, n7, signature2.proofs[j].seedInfoLen);
                int n8 = n7 + signature2.proofs[j].seedInfoLen;
                if (signature2.challengeP[indexOf(signature2.challengeC, this.numOpenedRounds, j)] != this.numMPCParties - 1) {
                    System.arraycopy(signature2.proofs[j].aux, 0, array, n8, this.andSizeBytes);
                    n8 += this.andSizeBytes;
                }
                System.arraycopy(signature2.proofs[j].input, 0, array, n8, this.stateSizeBytes);
                final int n9 = n8 + this.stateSizeBytes;
                System.arraycopy(signature2.proofs[j].msgs, 0, array, n9, this.andSizeBytes);
                final int n10 = n9 + this.andSizeBytes;
                System.arraycopy(signature2.proofs[j].C, 0, array, n10, this.digestSizeBytes);
                n7 = n10 + this.digestSizeBytes;
            }
        }
        return n7 - n;
    }
    
    private int sign_picnic1(final int[] array, final int[] array2, final int[] array3, final byte[] array4, final Signature signature) {
        final View[][] array5 = new View[this.numMPCRounds][3];
        final byte[][][] array6 = new byte[this.numMPCRounds][this.numMPCParties][this.digestSizeBytes];
        final byte[][][] array7 = new byte[this.numMPCRounds][3][this.UnruhGWithInputBytes];
        final byte[] computeSeeds = this.computeSeeds(array, array2, array3, array4);
        final int n = this.numMPCParties * this.seedSizeBytes;
        System.arraycopy(computeSeeds, n * this.numMPCRounds, signature.salt, 0, 32);
        final Tape tape = new Tape(this);
        final byte[] array8 = new byte[Math.max(9 * this.stateSizeBytes, this.stateSizeBytes + this.andSizeBytes)];
        for (int i = 0; i < this.numMPCRounds; ++i) {
            array5[i][0] = new View(this);
            array5[i][1] = new View(this);
            array5[i][2] = new View(this);
            for (int j = 0; j < 2; ++j) {
                if (!this.createRandomTape(computeSeeds, n * i + j * this.seedSizeBytes, signature.salt, i, j, array8, this.stateSizeBytes + this.andSizeBytes)) {
                    PicnicEngine.LOG.fine("createRandomTape failed");
                    return -1;
                }
                final int[] inputShare = array5[i][j].inputShare;
                Pack.littleEndianToInt(array8, 0, inputShare);
                Utils.zeroTrailingBits(inputShare, this.stateSizeBits);
                System.arraycopy(array8, this.stateSizeBytes, tape.tapes[j], 0, this.andSizeBytes);
            }
            if (!this.createRandomTape(computeSeeds, n * i + 2 * this.seedSizeBytes, signature.salt, i, 2, tape.tapes[2], this.andSizeBytes)) {
                PicnicEngine.LOG.fine("createRandomTape failed");
                return -1;
            }
            this.xor_three(array5[i][2].inputShare, array, array5[i][0].inputShare, array5[i][1].inputShare);
            tape.pos = 0;
            final int[] littleEndianToInt = Pack.littleEndianToInt(array8, 0, array8.length / 4);
            this.mpc_LowMC(tape, array5[i], array3, littleEndianToInt);
            Pack.intToLittleEndian(littleEndianToInt, array8, 0);
            final int[] array9 = new int[16];
            this.xor_three(array9, array5[i][0].outputShare, array5[i][1].outputShare, array5[i][2].outputShare);
            if (!subarrayEquals(array9, array2, this.stateSizeWords)) {
                PicnicEngine.LOG.fine("Simulation failed; output does not match public key (round = " + i + ")");
                return -1;
            }
            this.Commit(computeSeeds, n * i + 0 * this.seedSizeBytes, array5[i][0], array6[i][0]);
            this.Commit(computeSeeds, n * i + 1 * this.seedSizeBytes, array5[i][1], array6[i][1]);
            this.Commit(computeSeeds, n * i + 2 * this.seedSizeBytes, array5[i][2], array6[i][2]);
            if (this.transform == 1) {
                this.G(0, computeSeeds, n * i + 0 * this.seedSizeBytes, array5[i][0], array7[i][0]);
                this.G(1, computeSeeds, n * i + 1 * this.seedSizeBytes, array5[i][1], array7[i][1]);
                this.G(2, computeSeeds, n * i + 2 * this.seedSizeBytes, array5[i][2], array7[i][2]);
            }
        }
        this.H3(array2, array3, array5, array6, signature.challengeBits, signature.salt, array4, array7);
        for (int k = 0; k < this.numMPCRounds; ++k) {
            this.prove(signature.proofs[k], this.getChallenge(signature.challengeBits, k), computeSeeds, n * k, array5[k], array6[k], (byte[][])((this.transform != 1) ? null : array7[k]));
        }
        return 0;
    }
    
    void prove(final Signature.Proof proof, final int n, final byte[] array, final int n2, final View[] array2, final byte[][] array3, final byte[][] array4) {
        if (n == 0) {
            System.arraycopy(array, n2 + 0 * this.seedSizeBytes, proof.seed1, 0, this.seedSizeBytes);
            System.arraycopy(array, n2 + 1 * this.seedSizeBytes, proof.seed2, 0, this.seedSizeBytes);
        }
        else if (n == 1) {
            System.arraycopy(array, n2 + 1 * this.seedSizeBytes, proof.seed1, 0, this.seedSizeBytes);
            System.arraycopy(array, n2 + 2 * this.seedSizeBytes, proof.seed2, 0, this.seedSizeBytes);
        }
        else {
            if (n != 2) {
                PicnicEngine.LOG.fine("Invalid challenge");
                throw new IllegalArgumentException("challenge");
            }
            System.arraycopy(array, n2 + 2 * this.seedSizeBytes, proof.seed1, 0, this.seedSizeBytes);
            System.arraycopy(array, n2 + 0 * this.seedSizeBytes, proof.seed2, 0, this.seedSizeBytes);
        }
        if (n == 1 || n == 2) {
            System.arraycopy(array2[2].inputShare, 0, proof.inputShare, 0, this.stateSizeWords);
        }
        System.arraycopy(array2[(n + 1) % 3].communicatedBits, 0, proof.communicatedBits, 0, this.andSizeBytes);
        System.arraycopy(array3[(n + 2) % 3], 0, proof.view3Commitment, 0, this.digestSizeBytes);
        if (this.transform == 1) {
            System.arraycopy(array4[(n + 2) % 3], 0, proof.view3UnruhG, 0, (n == 0) ? this.UnruhGWithInputBytes : this.UnruhGWithoutInputBytes);
        }
    }
    
    private void H3(final int[] array, final int[] array2, final View[][] array3, final byte[][][] array4, final byte[] array5, final byte[] array6, final byte[] array7, final byte[][][] array8) {
        this.digest.update((byte)1);
        final byte[] array9 = new byte[this.stateSizeWords * 4];
        for (int i = 0; i < this.numMPCRounds; ++i) {
            for (int j = 0; j < 3; ++j) {
                Pack.intToLittleEndian(array3[i][j].outputShare, array9, 0);
                this.digest.update(array9, 0, this.stateSizeBytes);
            }
        }
        this.implH3(array, array2, array4, array5, array6, array7, array8);
    }
    
    private void H3(final int[] array, final int[] array2, final int[][][] array3, final byte[][][] array4, final byte[] array5, final byte[] array6, final byte[] array7, final byte[][][] array8) {
        this.digest.update((byte)1);
        final byte[] array9 = new byte[this.stateSizeWords * 4];
        for (int i = 0; i < this.numMPCRounds; ++i) {
            for (int j = 0; j < 3; ++j) {
                Pack.intToLittleEndian(array3[i][j], array9, 0);
                this.digest.update(array9, 0, this.stateSizeBytes);
            }
        }
        this.implH3(array, array2, array4, array5, array6, array7, array8);
    }
    
    private void implH3(final int[] array, final int[] array2, final byte[][][] array3, final byte[] array4, final byte[] array5, final byte[] array6, final byte[][][] array7) {
        final byte[] array8 = new byte[this.digestSizeBytes];
        array4[Utils.numBytes(this.numMPCRounds * 2) - 1] = 0;
        for (int i = 0; i < this.numMPCRounds; ++i) {
            for (int j = 0; j < 3; ++j) {
                this.digest.update(array3[i][j], 0, this.digestSizeBytes);
            }
        }
        if (this.transform == 1) {
            for (int k = 0; k < this.numMPCRounds; ++k) {
                for (int l = 0; l < 3; ++l) {
                    this.digest.update(array7[k][l], 0, (l == 2) ? this.UnruhGWithInputBytes : this.UnruhGWithoutInputBytes);
                }
            }
        }
        this.digest.update(Pack.intToLittleEndian(array), 0, this.stateSizeBytes);
        this.digest.update(Pack.intToLittleEndian(array2), 0, this.stateSizeBytes);
        this.digest.update(array5, 0, 32);
        this.digest.update(array6, 0, array6.length);
        this.digest.doFinal(array8, 0, this.digestSizeBytes);
        int n = 0;
        int n2 = 1;
        while (n2 != 0) {
            for (int n3 = 0; n3 < this.digestSizeBytes; ++n3) {
                final byte b = array8[n3];
                for (int n4 = 0; n4 < 8; n4 += 2) {
                    final int n5 = b >>> 6 - n4 & 0x3;
                    if (n5 < 3) {
                        this.setChallenge(array4, n, n5);
                        if (++n == this.numMPCRounds) {
                            n2 = 0;
                            break;
                        }
                    }
                }
                if (n2 == 0) {
                    break;
                }
            }
            if (n2 == 0) {
                break;
            }
            this.digest.update((byte)1);
            this.digest.update(array8, 0, this.digestSizeBytes);
            this.digest.doFinal(array8, 0, this.digestSizeBytes);
        }
    }
    
    private void setChallenge(final byte[] array, final int n, final int n2) {
        Utils.setBit(array, 2 * n, (byte)(n2 & 0x1));
        Utils.setBit(array, 2 * n + 1, (byte)(n2 >>> 1 & 0x1));
    }
    
    private void G(final int n, final byte[] array, final int n2, final View view, final byte[] array2) {
        int n3 = this.seedSizeBytes + this.andSizeBytes;
        this.digest.update((byte)5);
        this.digest.update(array, n2, this.seedSizeBytes);
        this.digest.doFinal(array2, 0, this.digestSizeBytes);
        this.digest.update(array2, 0, this.digestSizeBytes);
        if (n == 2) {
            this.digest.update(Pack.intToLittleEndian(view.inputShare), 0, this.stateSizeBytes);
            n3 += this.stateSizeBytes;
        }
        this.digest.update(view.communicatedBits, 0, this.andSizeBytes);
        this.digest.update(Pack.intToLittleEndian(n3), 0, 2);
        this.digest.doFinal(array2, 0, n3);
    }
    
    private void mpc_LowMC(final Tape tape, final View[] array, final int[] array2, final int[] array3) {
        Arrays.fill(array3, 0, array3.length, 0);
        this.mpc_xor_constant(array3, 3 * this.stateSizeWords, array2, 0, this.stateSizeWords);
        final KMatricesWithPointer kMatrix = this.lowmcConstants.KMatrix(this, 0);
        for (int i = 0; i < 3; ++i) {
            this.matrix_mul_offset(array3, i * this.stateSizeWords, array[i].inputShare, 0, kMatrix.getData(), kMatrix.getMatrixPointer());
        }
        this.mpc_xor(array3, array3, 3);
        for (int j = 1; j <= this.numRounds; ++j) {
            final KMatricesWithPointer kMatrix2 = this.lowmcConstants.KMatrix(this, j);
            for (int k = 0; k < 3; ++k) {
                this.matrix_mul_offset(array3, k * this.stateSizeWords, array[k].inputShare, 0, kMatrix2.getData(), kMatrix2.getMatrixPointer());
            }
            this.mpc_substitution(array3, tape, array);
            final KMatricesWithPointer lMatrix = this.lowmcConstants.LMatrix(this, j - 1);
            this.mpc_matrix_mul(array3, 3 * this.stateSizeWords, array3, 3 * this.stateSizeWords, lMatrix.getData(), lMatrix.getMatrixPointer(), 3);
            final KMatricesWithPointer rConstant = this.lowmcConstants.RConstant(this, j - 1);
            this.mpc_xor_constant(array3, 3 * this.stateSizeWords, rConstant.getData(), rConstant.getMatrixPointer(), this.stateSizeWords);
            this.mpc_xor(array3, array3, 3);
        }
        for (int l = 0; l < 3; ++l) {
            System.arraycopy(array3, (3 + l) * this.stateSizeWords, array[l].outputShare, 0, this.stateSizeWords);
        }
    }
    
    private void Commit(final byte[] array, final int n, final View view, final byte[] array2) {
        this.digest.update((byte)4);
        this.digest.update(array, n, this.seedSizeBytes);
        this.digest.doFinal(array2, 0, this.digestSizeBytes);
        this.digest.update((byte)0);
        this.digest.update(array2, 0, this.digestSizeBytes);
        this.digest.update(Pack.intToLittleEndian(view.inputShare), 0, this.stateSizeBytes);
        this.digest.update(view.communicatedBits, 0, this.andSizeBytes);
        this.digest.update(Pack.intToLittleEndian(view.outputShare), 0, this.stateSizeBytes);
        this.digest.doFinal(array2, 0, this.digestSizeBytes);
    }
    
    private void mpc_substitution(final int[] array, final Tape tape, final View[] array2) {
        final int[] array3 = new int[3];
        final int[] array4 = new int[3];
        final int[] array5 = new int[3];
        final int[] array6 = new int[3];
        final int[] array7 = new int[3];
        final int[] array8 = new int[3];
        for (int i = 0; i < this.numSboxes * 3; i += 3) {
            for (int j = 0; j < 3; ++j) {
                final int n = (3 + j) * this.stateSizeWords * 32;
                array3[j] = Utils.getBitFromWordArray(array, n + i + 2);
                array4[j] = Utils.getBitFromWordArray(array, n + i + 1);
                array5[j] = Utils.getBitFromWordArray(array, n + i);
            }
            this.mpc_AND(array3, array4, array6, tape, array2);
            this.mpc_AND(array4, array5, array7, tape, array2);
            this.mpc_AND(array5, array3, array8, tape, array2);
            for (int k = 0; k < 3; ++k) {
                final int n2 = (3 + k) * this.stateSizeWords * 32;
                Utils.setBitInWordArray(array, n2 + i + 2, array3[k] ^ array7[k]);
                Utils.setBitInWordArray(array, n2 + i + 1, array3[k] ^ array4[k] ^ array8[k]);
                Utils.setBitInWordArray(array, n2 + i, array3[k] ^ array4[k] ^ array5[k] ^ array6[k]);
            }
        }
    }
    
    private void mpc_AND(final int[] array, final int[] array2, final int[] array3, final Tape tape, final View[] array4) {
        final byte bit = Utils.getBit(tape.tapes[0], tape.pos);
        final byte bit2 = Utils.getBit(tape.tapes[1], tape.pos);
        final byte bit3 = Utils.getBit(tape.tapes[2], tape.pos);
        array3[0] = ((array[0] & array2[1]) ^ (array[1] & array2[0]) ^ (array[0] & array2[0]) ^ bit ^ bit2);
        array3[1] = ((array[1] & array2[2]) ^ (array[2] & array2[1]) ^ (array[1] & array2[1]) ^ bit2 ^ bit3);
        array3[2] = ((array[2] & array2[0]) ^ (array[0] & array2[2]) ^ (array[2] & array2[2]) ^ bit3 ^ bit);
        Utils.setBit(array4[0].communicatedBits, tape.pos, (byte)array3[0]);
        Utils.setBit(array4[1].communicatedBits, tape.pos, (byte)array3[1]);
        Utils.setBit(array4[2].communicatedBits, tape.pos, (byte)array3[2]);
        ++tape.pos;
    }
    
    private void mpc_xor(final int[] array, final int[] array2, final int n) {
        for (int i = 0; i < this.stateSizeWords * n; ++i) {
            final int n2 = n * this.stateSizeWords + i;
            array[n2] ^= array2[i];
        }
    }
    
    private void mpc_matrix_mul(final int[] array, final int n, final int[] array2, final int n2, final int[] array3, final int n3, final int n4) {
        for (int i = 0; i < n4; ++i) {
            this.matrix_mul_offset(array, n + i * this.stateSizeWords, array2, n2 + i * this.stateSizeWords, array3, n3);
        }
    }
    
    private void mpc_xor_constant(final int[] array, final int n, final int[] array2, final int n2, final int n3) {
        for (int i = 0; i < n3; ++i) {
            final int n4 = i + n;
            array[n4] ^= array2[i + n2];
        }
    }
    
    private boolean createRandomTape(final byte[] array, final int n, final byte[] array2, final int n2, final int n3, final byte[] array3, final int n4) {
        if (n4 < this.digestSizeBytes) {
            return false;
        }
        this.digest.update((byte)2);
        this.digest.update(array, n, this.seedSizeBytes);
        this.digest.doFinal(array3, 0, this.digestSizeBytes);
        this.digest.update(array3, 0, this.digestSizeBytes);
        this.digest.update(array2, 0, 32);
        this.digest.update(Pack.intToLittleEndian(n2), 0, 2);
        this.digest.update(Pack.intToLittleEndian(n3), 0, 2);
        this.digest.update(Pack.intToLittleEndian(n4), 0, 2);
        this.digest.doFinal(array3, 0, n4);
        return true;
    }
    
    private byte[] computeSeeds(final int[] array, final int[] array2, final int[] array3, final byte[] array4) {
        final byte[] array5 = new byte[this.seedSizeBytes * (this.numMPCParties * this.numMPCRounds) + 32];
        final byte[] array6 = new byte[32];
        this.updateDigest(array, array6);
        this.digest.update(array4, 0, array4.length);
        this.updateDigest(array2, array6);
        this.updateDigest(array3, array6);
        this.digest.update(Pack.intToLittleEndian(this.stateSizeBits), 0, 2);
        this.digest.doFinal(array5, 0, this.seedSizeBytes * (this.numMPCParties * this.numMPCRounds) + 32);
        return array5;
    }
    
    private boolean sign_picnic3(final int[] array, final int[] array2, final int[] array3, final byte[] array4, final Signature2 signature2) {
        final byte[] array5 = new byte[32 + this.seedSizeBytes];
        this.computeSaltAndRootSeed(array5, array, array2, array3, array4);
        final byte[] copyOfRange = Arrays.copyOfRange(array5, 32, array5.length);
        signature2.salt = Arrays.copyOfRange(array5, 0, 32);
        final Tree tree = new Tree(this, this.numMPCRounds, this.seedSizeBytes);
        tree.generateSeeds(copyOfRange, signature2.salt, 0);
        final byte[][] leaves = tree.getLeaves();
        final int leavesOffset = tree.getLeavesOffset();
        final Tape[] array6 = new Tape[this.numMPCRounds];
        final Tree[] array7 = new Tree[this.numMPCRounds];
        for (int i = 0; i < this.numMPCRounds; ++i) {
            array6[i] = new Tape(this);
            (array7[i] = new Tree(this, this.numMPCParties, this.seedSizeBytes)).generateSeeds(leaves[i + leavesOffset], signature2.salt, i);
            this.createRandomTapes(array6[i], array7[i].getLeaves(), array7[i].getLeavesOffset(), signature2.salt, i);
        }
        final byte[][] array8 = new byte[this.numMPCRounds][this.stateSizeWords * 4];
        final byte[] array9 = new byte[176];
        for (int j = 0; j < this.numMPCRounds; ++j) {
            array6[j].computeAuxTape(array8[j]);
        }
        final byte[][][] array10 = new byte[this.numMPCRounds][this.numMPCParties][this.digestSizeBytes];
        for (int k = 0; k < this.numMPCRounds; ++k) {
            for (int l = 0; l < this.numMPCParties - 1; ++l) {
                this.commit(array10[k][l], array7[k].getLeaf(l), null, signature2.salt, k, l);
            }
            final int n = this.numMPCParties - 1;
            this.getAuxBits(array9, array6[k]);
            this.commit(array10[k][n], array7[k].getLeaf(n), array9, signature2.salt, k, n);
        }
        final Msg[] array11 = new Msg[this.numMPCRounds];
        final int[] array12 = new int[this.stateSizeBits];
        for (int n2 = 0; n2 < this.numMPCRounds; ++n2) {
            array11[n2] = new Msg(this);
            final int[] littleEndianToInt = Pack.littleEndianToInt(array8[n2], 0, this.stateSizeWords);
            this.xor_array(littleEndianToInt, littleEndianToInt, array, 0);
            if (this.simulateOnline(littleEndianToInt, array6[n2], array12, array11[n2], array3, array2) != 0) {
                PicnicEngine.LOG.fine("MPC simulation failed, aborting signature");
                return false;
            }
            Pack.intToLittleEndian(littleEndianToInt, array8[n2], 0);
        }
        final byte[][] array13 = new byte[this.numMPCRounds][this.digestSizeBytes];
        final byte[][] array14 = new byte[this.numMPCRounds][this.digestSizeBytes];
        for (int n3 = 0; n3 < this.numMPCRounds; ++n3) {
            this.commit_h(array13[n3], array10[n3]);
            this.commit_v(array14[n3], array8[n3], array11[n3]);
        }
        final Tree tree2 = new Tree(this, this.numMPCRounds, this.digestSizeBytes);
        tree2.buildMerkleTree(array14, signature2.salt);
        signature2.challengeC = new int[this.numOpenedRounds];
        signature2.challengeP = new int[this.numOpenedRounds];
        this.HCP(signature2.challengeHash = new byte[this.digestSizeBytes], signature2.challengeC, signature2.challengeP, array13, tree2.nodes[0], signature2.salt, array2, array3, array4);
        final int n4 = this.numMPCRounds - this.numOpenedRounds;
        final int[] missingLeavesList = this.getMissingLeavesList(signature2.challengeC);
        final int[] array15 = { 0 };
        signature2.cvInfo = tree2.openMerkleTree(missingLeavesList, n4, array15);
        signature2.cvInfoLen = array15[0];
        signature2.iSeedInfo = new byte[this.numMPCRounds * this.seedSizeBytes];
        signature2.iSeedInfoLen = tree.revealSeeds(signature2.challengeC, this.numOpenedRounds, signature2.iSeedInfo, this.numMPCRounds * this.seedSizeBytes);
        signature2.proofs = new Signature2.Proof2[this.numMPCRounds];
        for (int n5 = 0; n5 < this.numMPCRounds; ++n5) {
            if (this.contains(signature2.challengeC, this.numOpenedRounds, n5)) {
                signature2.proofs[n5] = new Signature2.Proof2(this);
                final int index = indexOf(signature2.challengeC, this.numOpenedRounds, n5);
                final int[] array16 = { signature2.challengeP[index] };
                signature2.proofs[n5].seedInfo = new byte[this.numMPCParties * this.seedSizeBytes];
                signature2.proofs[n5].seedInfoLen = array7[n5].revealSeeds(array16, 1, signature2.proofs[n5].seedInfo, this.numMPCParties * this.seedSizeBytes);
                if (signature2.challengeP[index] != this.numMPCParties - 1) {
                    this.getAuxBits(signature2.proofs[n5].aux, array6[n5]);
                }
                System.arraycopy(array8[n5], 0, signature2.proofs[n5].input, 0, this.stateSizeBytes);
                System.arraycopy(array11[n5].msgs[signature2.challengeP[index]], 0, signature2.proofs[n5].msgs, 0, this.andSizeBytes);
                System.arraycopy(array10[n5][signature2.challengeP[index]], 0, signature2.proofs[n5].C, 0, this.digestSizeBytes);
            }
        }
        return true;
    }
    
    static int indexOf(final int[] array, final int n, final int n2) {
        for (int i = 0; i < n; ++i) {
            if (array[i] == n2) {
                return i;
            }
        }
        return -1;
    }
    
    private int[] getMissingLeavesList(final int[] array) {
        final int[] array2 = new int[this.numMPCRounds - this.numOpenedRounds];
        int n = 0;
        for (int i = 0; i < this.numMPCRounds; ++i) {
            if (!this.contains(array, this.numOpenedRounds, i)) {
                array2[n] = i;
                ++n;
            }
        }
        return array2;
    }
    
    private void HCP(final byte[] array, final int[] array2, final int[] array3, final byte[][] array4, final byte[] array5, final byte[] array6, final int[] array7, final int[] array8, final byte[] array9) {
        for (int i = 0; i < this.numMPCRounds; ++i) {
            this.digest.update(array4[i], 0, this.digestSizeBytes);
        }
        final byte[] array10 = new byte[32];
        this.digest.update(array5, 0, this.digestSizeBytes);
        this.digest.update(array6, 0, 32);
        this.updateDigest(array7, array10);
        this.updateDigest(array8, array10);
        this.digest.update(array9, 0, array9.length);
        this.digest.doFinal(array, 0, this.digestSizeBytes);
        if (array2 != null && array3 != null) {
            this.expandChallengeHash(array, array2, array3);
        }
    }
    
    static int bitsToChunks(final int n, final byte[] array, final int n2, final int[] array2) {
        if (n > n2 * 8) {
            return 0;
        }
        final int n3 = n2 * 8 / n;
        for (int i = 0; i < n3; ++i) {
            array2[i] = 0;
            for (int j = 0; j < n; ++j) {
                final int n4 = i;
                array2[n4] += Utils.getBit(array, i * n + j) << j;
            }
        }
        return n3;
    }
    
    static int appendUnique(final int[] array, final int n, final int n2) {
        if (n2 == 0) {
            array[n2] = n;
            return n2 + 1;
        }
        for (int i = 0; i < n2; ++i) {
            if (array[i] == n) {
                return n2;
            }
        }
        array[n2] = n;
        return n2 + 1;
    }
    
    private void expandChallengeHash(final byte[] array, final int[] array2, final int[] array3) {
        final int ceil_log2 = Utils.ceil_log2(this.numMPCRounds);
        final int ceil_log3 = Utils.ceil_log2(this.numMPCParties);
        final int[] array4 = new int[this.digestSizeBytes * 8 / Math.min(ceil_log2, ceil_log3)];
        final byte[] array5 = new byte[64];
        System.arraycopy(array, 0, array5, 0, this.digestSizeBytes);
        int i = 0;
        while (i < this.numOpenedRounds) {
            for (int bitsToChunks = bitsToChunks(ceil_log2, array5, this.digestSizeBytes, array4), j = 0; j < bitsToChunks; ++j) {
                if (array4[j] < this.numMPCRounds) {
                    i = appendUnique(array2, array4[j], i);
                }
                if (i == this.numOpenedRounds) {
                    break;
                }
            }
            this.digest.update((byte)1);
            this.digest.update(array5, 0, this.digestSizeBytes);
            this.digest.doFinal(array5, 0, this.digestSizeBytes);
        }
        int k = 0;
        while (k < this.numOpenedRounds) {
            for (int bitsToChunks2 = bitsToChunks(ceil_log3, array5, this.digestSizeBytes, array4), l = 0; l < bitsToChunks2; ++l) {
                if (array4[l] < this.numMPCParties) {
                    array3[k] = array4[l];
                    ++k;
                }
                if (k == this.numOpenedRounds) {
                    break;
                }
            }
            this.digest.update((byte)1);
            this.digest.update(array5, 0, this.digestSizeBytes);
            this.digest.doFinal(array5, 0, this.digestSizeBytes);
        }
    }
    
    private void commit_h(final byte[] array, final byte[][] array2) {
        for (int i = 0; i < this.numMPCParties; ++i) {
            this.digest.update(array2[i], 0, this.digestSizeBytes);
        }
        this.digest.doFinal(array, 0, this.digestSizeBytes);
    }
    
    private void commit_v(final byte[] array, final byte[] array2, final Msg msg) {
        this.digest.update(array2, 0, this.stateSizeBytes);
        for (int i = 0; i < this.numMPCParties; ++i) {
            this.digest.update(msg.msgs[i], 0, Utils.numBytes(msg.pos));
        }
        this.digest.doFinal(array, 0, this.digestSizeBytes);
    }
    
    private int simulateOnline(final int[] array, final Tape tape, final int[] array2, final Msg msg, final int[] array3, final int[] array4) {
        int n = 0;
        final int[] array5 = new int[16];
        final int[] array6 = new int[16];
        final KMatricesWithPointer kMatrix = this.lowmcConstants.KMatrix(this, 0);
        this.matrix_mul(array5, array, kMatrix.getData(), kMatrix.getMatrixPointer());
        this.xor_array(array6, array5, array3, 0);
        for (int i = 1; i <= this.numRounds; ++i) {
            this.tapesToWords(array2, tape);
            this.mpc_sbox(array6, array2, tape, msg);
            final KMatricesWithPointer lMatrix = this.lowmcConstants.LMatrix(this, i - 1);
            this.matrix_mul(array6, array6, lMatrix.getData(), lMatrix.getMatrixPointer());
            final KMatricesWithPointer rConstant = this.lowmcConstants.RConstant(this, i - 1);
            this.xor_array(array6, array6, rConstant.getData(), rConstant.getMatrixPointer());
            final KMatricesWithPointer kMatrix2 = this.lowmcConstants.KMatrix(this, i);
            this.matrix_mul(array5, array, kMatrix2.getData(), kMatrix2.getMatrixPointer());
            this.xor_array(array6, array5, array6, 0);
        }
        if (!subarrayEquals(array6, array4, this.stateSizeWords)) {
            n = -1;
        }
        return n;
    }
    
    private void createRandomTapes(final Tape tape, final byte[][] array, final int n, final byte[] array2, final int n2) {
        final int n3 = 2 * this.andSizeBytes;
        for (int i = 0; i < this.numMPCParties; ++i) {
            this.digest.update(array[i + n], 0, this.seedSizeBytes);
            this.digest.update(array2, 0, 32);
            this.digest.update(Pack.intToLittleEndian(n2), 0, 2);
            this.digest.update(Pack.intToLittleEndian(i), 0, 2);
            this.digest.doFinal(tape.tapes[i], 0, n3);
        }
    }
    
    private static boolean subarrayEquals(final byte[] array, final byte[] array2, final int n) {
        if (array.length < n || array2.length < n) {
            return false;
        }
        for (int i = 0; i < n; ++i) {
            if (array[i] != array2[i]) {
                return false;
            }
        }
        return true;
    }
    
    private static boolean subarrayEquals(final int[] array, final int[] array2, final int n) {
        if (array.length < n || array2.length < n) {
            return false;
        }
        for (int i = 0; i < n; ++i) {
            if (array[i] != array2[i]) {
                return false;
            }
        }
        return true;
    }
    
    static int extend(final int n) {
        return ~(n - 1);
    }
    
    private void wordToMsgs(final int n, final Msg msg) {
        for (int i = 0; i < this.numMPCParties; ++i) {
            Utils.setBit(msg.msgs[i], msg.pos, (byte)Utils.getBit(n, i));
        }
        ++msg.pos;
    }
    
    private int mpc_AND(final int n, final int n2, final int n3, final int n4, final Tape tape, final Msg msg) {
        int setBit = (extend(n) & n4) ^ (extend(n2) & n3) ^ tape.tapesToWord();
        if (msg.unopened >= 0) {
            setBit = Utils.setBit(setBit, msg.unopened, Utils.getBit(msg.msgs[msg.unopened], msg.pos));
        }
        this.wordToMsgs(setBit, msg);
        return Utils.parity16(setBit) ^ (n & n2);
    }
    
    private void mpc_sbox(final int[] array, final int[] array2, final Tape tape, final Msg msg) {
        for (int i = 0; i < this.numSboxes * 3; i += 3) {
            final int bitFromWordArray = Utils.getBitFromWordArray(array, i + 2);
            final int n = array2[i + 2];
            final int bitFromWordArray2 = Utils.getBitFromWordArray(array, i + 1);
            final int n2 = array2[i + 1];
            final int bitFromWordArray3 = Utils.getBitFromWordArray(array, i);
            final int n3 = array2[i];
            final int mpc_AND = this.mpc_AND(bitFromWordArray, bitFromWordArray2, n, n2, tape, msg);
            final int mpc_AND2 = this.mpc_AND(bitFromWordArray2, bitFromWordArray3, n2, n3, tape, msg);
            final int mpc_AND3 = this.mpc_AND(bitFromWordArray3, bitFromWordArray, n3, n, tape, msg);
            final int n4 = bitFromWordArray ^ mpc_AND2;
            final int n5 = bitFromWordArray ^ bitFromWordArray2 ^ mpc_AND3;
            final int n6 = bitFromWordArray ^ bitFromWordArray2 ^ bitFromWordArray3 ^ mpc_AND;
            Utils.setBitInWordArray(array, i + 2, n4);
            Utils.setBitInWordArray(array, i + 1, n5);
            Utils.setBitInWordArray(array, i, n6);
        }
    }
    
    protected void aux_mpc_sbox(final int[] array, final int[] array2, final Tape tape) {
        for (int i = 0; i < this.numSboxes * 3; i += 3) {
            final int bitFromWordArray = Utils.getBitFromWordArray(array, i + 2);
            final int bitFromWordArray2 = Utils.getBitFromWordArray(array, i + 1);
            final int bitFromWordArray3 = Utils.getBitFromWordArray(array, i);
            final int bitFromWordArray4 = Utils.getBitFromWordArray(array2, i + 2);
            final int bitFromWordArray5 = Utils.getBitFromWordArray(array2, i + 1);
            final int n = Utils.getBitFromWordArray(array2, i) ^ bitFromWordArray ^ bitFromWordArray2 ^ bitFromWordArray3;
            final int n2 = bitFromWordArray4 ^ bitFromWordArray;
            final int n3 = bitFromWordArray5 ^ bitFromWordArray ^ bitFromWordArray2;
            this.aux_mpc_AND(bitFromWordArray, bitFromWordArray2, n, tape);
            this.aux_mpc_AND(bitFromWordArray2, bitFromWordArray3, n2, tape);
            this.aux_mpc_AND(bitFromWordArray3, bitFromWordArray, n3, tape);
        }
    }
    
    private void aux_mpc_AND(final int n, final int n2, final int n3, final Tape tape) {
        final int n4 = this.numMPCParties - 1;
        Utils.setBit(tape.tapes[n4], tape.pos - 1, (byte)(((n & n2) ^ (Utils.parity16(tape.tapesToWord()) ^ Utils.getBit(tape.tapes[n4], tape.pos - 1)) ^ n3) & 0xFF));
    }
    
    private boolean contains(final int[] array, final int n, final int n2) {
        for (int i = 0; i < n; ++i) {
            if (array[i] == n2) {
                return true;
            }
        }
        return false;
    }
    
    private void tapesToWords(final int[] array, final Tape tape) {
        for (int i = 0; i < this.stateSizeBits; ++i) {
            array[i] = tape.tapesToWord();
        }
    }
    
    private void getAuxBits(final byte[] array, final Tape tape) {
        final byte[] array2 = tape.tapes[this.numMPCParties - 1];
        final int stateSizeBits = this.stateSizeBits;
        int n = 0;
        int n2 = 0;
        for (int i = 0; i < this.numRounds; ++i) {
            n2 += stateSizeBits;
            for (int j = 0; j < stateSizeBits; ++j) {
                Utils.setBit(array, n++, Utils.getBit(array2, n2++));
            }
        }
    }
    
    private void commit(final byte[] array, final byte[] array2, final byte[] array3, final byte[] array4, final int n, final int n2) {
        this.digest.update(array2, 0, this.seedSizeBytes);
        if (array3 != null) {
            this.digest.update(array3, 0, this.andSizeBytes);
        }
        this.digest.update(array4, 0, 32);
        this.digest.update(Pack.intToLittleEndian(n), 0, 2);
        this.digest.update(Pack.intToLittleEndian(n2), 0, 2);
        this.digest.doFinal(array, 0, this.digestSizeBytes);
    }
    
    private void computeSaltAndRootSeed(final byte[] array, final int[] array2, final int[] array3, final int[] array4, final byte[] array5) {
        final byte[] array6 = new byte[32];
        this.updateDigest(array2, array6);
        this.digest.update(array5, 0, array5.length);
        this.updateDigest(array3, array6);
        this.updateDigest(array4, array6);
        Pack.shortToLittleEndian((short)this.stateSizeBits, array6, 0);
        this.digest.update(array6, 0, 2);
        this.digest.doFinal(array, 0, array.length);
    }
    
    private void updateDigest(final int[] array, final byte[] array2) {
        Pack.intToLittleEndian(array, array2, 0);
        this.digest.update(array2, 0, this.stateSizeBytes);
    }
    
    static boolean is_picnic3(final int n) {
        return n == 7 || n == 8 || n == 9;
    }
    
    public void crypto_sign_keypair(final byte[] array, final byte[] array2, final SecureRandom secureRandom) {
        final byte[] array3 = new byte[this.stateSizeWords * 4];
        final byte[] array4 = new byte[this.stateSizeWords * 4];
        final byte[] array5 = new byte[this.stateSizeWords * 4];
        this.picnic_keygen(array3, array4, array5, secureRandom);
        this.picnic_write_public_key(array4, array3, array);
        this.picnic_write_private_key(array5, array4, array3, array2);
    }
    
    private int picnic_write_private_key(final byte[] array, final byte[] array2, final byte[] array3, final byte[] array4) {
        final int n = 1 + 3 * this.stateSizeBytes;
        if (array4.length < n) {
            PicnicEngine.LOG.fine("Failed writing private key!");
            return -1;
        }
        array4[0] = (byte)this.parameters;
        System.arraycopy(array, 0, array4, 1, this.stateSizeBytes);
        System.arraycopy(array2, 0, array4, 1 + this.stateSizeBytes, this.stateSizeBytes);
        System.arraycopy(array3, 0, array4, 1 + 2 * this.stateSizeBytes, this.stateSizeBytes);
        return n;
    }
    
    private int picnic_write_public_key(final byte[] array, final byte[] array2, final byte[] array3) {
        final int n = 1 + 2 * this.stateSizeBytes;
        if (array3.length < n) {
            PicnicEngine.LOG.fine("Failed writing public key!");
            return -1;
        }
        array3[0] = (byte)this.parameters;
        System.arraycopy(array, 0, array3, 1, this.stateSizeBytes);
        System.arraycopy(array2, 0, array3, 1 + this.stateSizeBytes, this.stateSizeBytes);
        return n;
    }
    
    private void picnic_keygen(final byte[] bytes, final byte[] array, final byte[] bytes2, final SecureRandom secureRandom) {
        final int[] array2 = new int[bytes2.length / 4];
        final int[] array3 = new int[bytes.length / 4];
        final int[] array4 = new int[array.length / 4];
        secureRandom.nextBytes(bytes2);
        Pack.littleEndianToInt(bytes2, 0, array2);
        Utils.zeroTrailingBits(array2, this.stateSizeBits);
        secureRandom.nextBytes(bytes);
        Pack.littleEndianToInt(bytes, 0, array3);
        Utils.zeroTrailingBits(array3, this.stateSizeBits);
        this.LowMCEnc(array3, array4, array2);
        Pack.intToLittleEndian(array2, bytes2, 0);
        Pack.intToLittleEndian(array3, bytes, 0);
        Pack.intToLittleEndian(array4, array, 0);
    }
    
    private void LowMCEnc(final int[] array, final int[] array2, final int[] array3) {
        final int[] array4 = new int[16];
        if (array != array2) {
            System.arraycopy(array, 0, array2, 0, this.stateSizeWords);
        }
        final KMatricesWithPointer kMatrix = this.lowmcConstants.KMatrix(this, 0);
        this.matrix_mul(array4, array3, kMatrix.getData(), kMatrix.getMatrixPointer());
        this.xor_array(array2, array2, array4, 0);
        for (int i = 1; i <= this.numRounds; ++i) {
            final KMatricesWithPointer kMatrix2 = this.lowmcConstants.KMatrix(this, i);
            this.matrix_mul(array4, array3, kMatrix2.getData(), kMatrix2.getMatrixPointer());
            this.substitution(array2);
            final KMatricesWithPointer lMatrix = this.lowmcConstants.LMatrix(this, i - 1);
            this.matrix_mul(array2, array2, lMatrix.getData(), lMatrix.getMatrixPointer());
            final KMatricesWithPointer rConstant = this.lowmcConstants.RConstant(this, i - 1);
            this.xor_array(array2, array2, rConstant.getData(), rConstant.getMatrixPointer());
            this.xor_array(array2, array2, array4, 0);
        }
    }
    
    private void substitution(final int[] array) {
        for (int i = 0; i < this.numSboxes * 3; i += 3) {
            final int bitFromWordArray = Utils.getBitFromWordArray(array, i + 2);
            final int bitFromWordArray2 = Utils.getBitFromWordArray(array, i + 1);
            final int bitFromWordArray3 = Utils.getBitFromWordArray(array, i);
            Utils.setBitInWordArray(array, i + 2, bitFromWordArray ^ (bitFromWordArray2 & bitFromWordArray3));
            Utils.setBitInWordArray(array, i + 1, bitFromWordArray ^ bitFromWordArray2 ^ (bitFromWordArray & bitFromWordArray3));
            Utils.setBitInWordArray(array, i, bitFromWordArray ^ bitFromWordArray2 ^ bitFromWordArray3 ^ (bitFromWordArray & bitFromWordArray2));
        }
    }
    
    private void xor_three(final int[] array, final int[] array2, final int[] array3, final int[] array4) {
        for (int i = 0; i < this.stateSizeWords; ++i) {
            array[i] = (array2[i] ^ array3[i] ^ array4[i]);
        }
    }
    
    protected void xor_array(final int[] array, final int[] array2, final int[] array3, final int n) {
        for (int i = 0; i < this.stateSizeWords; ++i) {
            array[i] = (array2[i] ^ array3[i + n]);
        }
    }
    
    protected void matrix_mul(final int[] array, final int[] array2, final int[] array3, final int n) {
        this.matrix_mul_offset(array, 0, array2, 0, array3, n);
    }
    
    protected void matrix_mul_offset(final int[] array, final int n, final int[] array2, final int n2, final int[] array3, final int n3) {
        final int[] array4 = new int[16];
        array4[this.stateSizeWords - 1] = 0;
        final int n4 = this.stateSizeBits / 32;
        final int n5 = this.stateSizeWords * 32 - this.stateSizeBits;
        final int bitPermuteStepSimple = Bits.bitPermuteStepSimple(Bits.bitPermuteStepSimple(Bits.bitPermuteStepSimple(-1 >>> n5, 1431655765, 1), 858993459, 2), 252645135, 4);
        for (int i = 0; i < this.stateSizeBits; ++i) {
            int n6 = 0;
            for (int j = 0; j < n4; ++j) {
                n6 ^= (array2[n2 + j] & array3[n3 + (i * this.stateSizeWords + j)]);
            }
            if (n5 > 0) {
                n6 ^= (array2[n2 + n4] & array3[n3 + (i * this.stateSizeWords + n4)] & bitPermuteStepSimple);
            }
            Utils.setBit(array4, i, Utils.parity32(n6));
        }
        System.arraycopy(array4, 0, array, n, this.stateSizeWords);
    }
    
    static {
        LOG = Logger.getLogger(PicnicEngine.class.getName());
    }
}
