package org.bouncycastle.pqc.legacy.crypto.mceliece;

import java.security.SecureRandom;

import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator;
import org.bouncycastle.crypto.KeyGenerationParameters;
import org.bouncycastle.pqc.legacy.math.linearalgebra.GF2Matrix;
import org.bouncycastle.pqc.legacy.math.linearalgebra.GF2mField;
import org.bouncycastle.pqc.legacy.math.linearalgebra.GoppaCode;
import org.bouncycastle.pqc.legacy.math.linearalgebra.GoppaCode.MaMaPe;
import org.bouncycastle.pqc.legacy.math.linearalgebra.Permutation;
import org.bouncycastle.pqc.legacy.math.linearalgebra.PolynomialGF2mSmallM;
import org.bouncycastle.pqc.legacy.math.linearalgebra.PolynomialRingGF2m;


/**
 * This class implements key pair generation of the McEliece Public Key
 * Cryptosystem (McEliecePKC).
 */
public class McElieceKeyPairGenerator
    implements AsymmetricCipherKeyPairGenerator
{


    public McElieceKeyPairGenerator()
    {

    }


    /**
     * The OID of the algorithm.
     */
    private static final String OID = "1.3.6.1.4.1.8301.3.1.3.4.1";

    private McElieceKeyGenerationParameters mcElieceParams;

    // the extension degree of the finite field GF(2^m)
    private int m;

    // the length of the code
    private int n;

    // the error correction capability
    private int t;

    // the field polynomial
    private int fieldPoly;

    // the source of randomness
    private SecureRandom random;

    // flag indicating whether the key pair generator has been initialized
    private boolean initialized = false;


    /**
     * Default initialization of the key pair generator.
     */
    private void initializeDefault()
    {
        McElieceKeyGenerationParameters mcParams = new McElieceKeyGenerationParameters(null, new McElieceParameters());
        initialize(mcParams);
    }

    private void initialize(
        KeyGenerationParameters param)
    {
        this.mcElieceParams = (McElieceKeyGenerationParameters)param;
        this.random = param.getRandom();

        this.m = this.mcElieceParams.getParameters().getM();
        this.n = this.mcElieceParams.getParameters().getN();
        this.t = this.mcElieceParams.getParameters().getT();
        this.fieldPoly = this.mcElieceParams.getParameters().getFieldPoly();
        this.initialized = true;
    }


    private AsymmetricCipherKeyPair genKeyPair()
    {

        if (!initialized)
        {
            initializeDefault();
        }

        // finite field GF(2^m)
        GF2mField field = new GF2mField(m, fieldPoly);

        // irreducible Goppa polynomial
        PolynomialGF2mSmallM gp = new PolynomialGF2mSmallM(field, t,
            PolynomialGF2mSmallM.RANDOM_IRREDUCIBLE_POLYNOMIAL, random);
        PolynomialRingGF2m ring = new PolynomialRingGF2m(field, gp);

        // matrix used to compute square roots in (GF(2^m))^t
        PolynomialGF2mSmallM[] sqRootMatrix = ring.getSquareRootMatrix();

        // generate canonical check matrix
        GF2Matrix h = GoppaCode.createCanonicalCheckMatrix(field, gp);

        // compute short systematic form of check matrix
        MaMaPe mmp = GoppaCode.computeSystematicForm(h, random);
        GF2Matrix shortH = mmp.getSecondMatrix();
        Permutation p1 = mmp.getPermutation();

        // compute short systematic form of generator matrix
        GF2Matrix shortG = (GF2Matrix)shortH.computeTranspose();

        // extend to full systematic form
        GF2Matrix gPrime = shortG.extendLeftCompactForm();

        // obtain number of rows of G (= dimension of the code)
        int k = shortG.getNumRows();

        // generate random invertible (k x k)-matrix S and its inverse S^-1
        GF2Matrix[] matrixSandInverse = GF2Matrix
            .createRandomRegularMatrixAndItsInverse(k, random);

        // generate random permutation P2
        Permutation p2 = new Permutation(n, random);

        // compute public matrix G=S*G'*P2
        GF2Matrix g = (GF2Matrix)matrixSandInverse[0].rightMultiply(gPrime);
        g = (GF2Matrix)g.rightMultiply(p2);


        // generate keys
        McEliecePublicKeyParameters pubKey = new McEliecePublicKeyParameters(n, t, g);
        McEliecePrivateKeyParameters privKey = new McEliecePrivateKeyParameters(n, k, field, gp, p1, p2, matrixSandInverse[1]);

        // return key pair
        return new AsymmetricCipherKeyPair(pubKey, privKey);
    }

    public void init(KeyGenerationParameters param)
    {
        this.initialize(param);
    }

    public AsymmetricCipherKeyPair generateKeyPair()
    {
        return genKeyPair();
    }

}
