/*
 * Copyright 2018 Scytl Secure Electronic Voting SA
 *
 * All rights reserved
 *
 * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
 */
package com.scytl.products.ov.config.shares.it;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.security.auth.x500.X500Principal;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;

import com.scytl.cryptolib.api.asymmetric.AsymmetricServiceAPI;
import com.scytl.cryptolib.api.elgamal.ElGamalServiceAPI;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.asymmetric.service.AsymmetricService;
import com.scytl.cryptolib.elgamal.bean.ElGamalEncrypterValues;
import com.scytl.cryptolib.elgamal.bean.ElGamalEncryptionParameters;
import com.scytl.cryptolib.elgamal.bean.ElGamalPrivateKey;
import com.scytl.cryptolib.elgamal.bean.ElGamalPublicKey;
import com.scytl.cryptolib.elgamal.cryptoapi.CryptoAPIElGamalDecrypter;
import com.scytl.cryptolib.elgamal.cryptoapi.CryptoAPIElGamalEncrypter;
import com.scytl.cryptolib.elgamal.service.ElGamalService;
import com.scytl.cryptolib.mathematical.groups.impl.ZpGroupElement;
import com.scytl.cryptolib.mathematical.groups.impl.ZpSubgroup;
import com.scytl.products.ov.config.constants.Constants;
import com.scytl.products.ov.config.shares.domain.CreateSharesOperationContext;
import com.scytl.products.ov.config.shares.domain.ReadSharesOperationContext;
import com.scytl.products.ov.config.shares.domain.SharesType;
import com.scytl.products.ov.config.shares.exception.SharesException;
import com.scytl.products.ov.config.shares.handler.CreateSharesHandler;
import com.scytl.products.ov.config.shares.handler.ReadSharesHandler;
import com.scytl.products.ov.config.shares.handler.StatelessReadSharesHandler;
import com.scytl.products.ov.config.shares.keys.PrivateKeySerializer;
import com.scytl.products.ov.config.shares.keys.elgamal.ElGamalKeyPairGenerator;
import com.scytl.products.ov.config.shares.keys.elgamal.ElGamalPrivateKeyAdapter;
import com.scytl.products.ov.config.shares.keys.elgamal.ElGamalPrivateKeySerializer;
import com.scytl.products.ov.config.shares.keys.elgamal.ElGamalPublicKeyAdapter;
import com.scytl.products.ov.config.shares.keys.rsa.RSAKeyPairGenerator;
import com.scytl.products.ov.config.shares.keys.rsa.RSAPrivateKeySerializer;
import com.scytl.products.ov.config.shares.service.PrivateKeySharesService;
import com.scytl.products.ov.config.shares.service.SmartCardService;
import com.scytl.products.ov.config.shares.service.SmartCardServiceFactory;
import com.scytl.products.ov.csr.CSRGenerator;
import com.scytl.products.ov.utils.PemUtils;

@Ignore
public class SharesITest {

    // common to RSA and ElGamal tests

    private static String oldPinPuk = "222222";

    private static String newPinPuk = "222222";

    private static KeyPair _signAuthorityKeyPair;

    private static Runnable _finalRunnable;

    private static SmartCardService _smartcardService;

    private static ReadSharesHandler _readSharesHandler;

    private static StatelessReadSharesHandler _statelessReadSharesHandler;

    // only used by RSA test

    private static CreateSharesHandler _createSharesHandlerRSA;

    private static AsymmetricServiceAPI _asymmetricServiceAPI;

    private static CSRGenerator _csrGenerator;

    private static X500Principal _subject;

    private static String _secretDataAsString = "This is a very important secret. This must not be made public.";

    // only used by ElGamal test

    private static CreateSharesHandler _createSharesHandlerElGamal;

    private static ElGamalServiceAPI _elgamalServiceAPI;

    private static ZpGroupElement _secretDataAsGroupElement;

    @BeforeClass
    public static void init() throws GeneralCryptoLibException, SharesException {

        Security.addProvider(new BouncyCastleProvider());

        _asymmetricServiceAPI = new AsymmetricService();

        _smartcardService = SmartCardServiceFactory.getSmartCardService(true);

        _elgamalServiceAPI = new ElGamalService();

        _csrGenerator = new CSRGenerator();

        _signAuthorityKeyPair = _asymmetricServiceAPI.getKeyPairForSigning();

        _finalRunnable = () -> {
            /* Do not create anything fot this test */};

        // initialise for Elgamal (Electoral Board)

        // ENCRYPTION PARAMETERS WHERE P IS 2048 BITS, Q IS 256 BITS (EXPONENT
        // IS 32 BYTES)
        // BigInteger p =
        // new BigInteger(
        // "27839315951550607954908947302805005270748306495150332019506497627681949106676211603621655834342899312853752944034571664077675893773681536386846966667523575670806834175256557051826034202665533443825675341545392523192426729654012224826614092343340720174718101364493840811991904431138360523983907332811743305613997816019580431245599135029769534308919329503387684189735998288344293295738991424137467298627689337334152522616643588240444981657026169355687112876037117176846382595626492699708769169931274950646847052618070904638628820367215645566336974910524181637499593007713578628942182502228080597082761613515206700523109");
        // BigInteger q = new
        // BigInteger("72672713147406890228989244889596588007200985967243596643124233889542687254499");
        // BigInteger g =
        // new BigInteger(
        // "4267702297229469673790018703734877296807681845981859915554469538283570502856417708468191328245393782101264664502289657179067509176893351929373100181771085681303723016005109429862508282653479146065243990330272150374977895870396872387540216688889850476152205361728439710042818398798929578066878456091896533097188888338823697398469244803574755568190747136605054794708632236694927928994592574083595309695961503955530493578418229984920184090088732231205782707337157009325887375886691582508754748112217552487874508954018581071754592385039406544226588417197430102130453807486352982973717703392982010821660259571513106072633");

        // ENCRYPTION WHERE P AND Q HAVE 2048 BITS (EXPONENT IS 256 BYTES)
        BigInteger p = new BigInteger(
            "16370518994319586760319791526293535327576438646782139419846004180837103527129035954742043590609421369665944746587885814920851694546456891767644945459124422553763416586515339978014154452159687109161090635367600349264934924141746082060353483306855352192358732451955232000593777554431798981574529854314651092086488426390776811367125009551346089319315111509277347117467107914073639456805159094562593954195960531136052208019343392906816001017488051366518122404819967204601427304267380238263913892658950281593755894747339126531018026798982785331079065126375455293409065540731646939808640273393855256230820509217411510058759");
        BigInteger q = new BigInteger(
            "8185259497159793380159895763146767663788219323391069709923002090418551763564517977371021795304710684832972373293942907460425847273228445883822472729562211276881708293257669989007077226079843554580545317683800174632467462070873041030176741653427676096179366225977616000296888777215899490787264927157325546043244213195388405683562504775673044659657555754638673558733553957036819728402579547281296977097980265568026104009671696453408000508744025683259061202409983602300713652133690119131956946329475140796877947373669563265509013399491392665539532563187727646704532770365823469904320136696927628115410254608705755029379");
        BigInteger g = new BigInteger("2");

        ZpSubgroup group = new ZpSubgroup(g, p, q);

        ElGamalEncryptionParameters elGamalEncryptionParameters = new ElGamalEncryptionParameters(p, q, g);

        ElGamalKeyPairGenerator elGamalKeyPairGenerator =
            new ElGamalKeyPairGenerator(elGamalEncryptionParameters, 1, _elgamalServiceAPI);

        ElGamalPrivateKeySerializer elGamalPrivateKeySerializer = new ElGamalPrivateKeySerializer();

        PrivateKeySharesService privateKeySharesServiceElGamal =
            new PrivateKeySharesService(elGamalPrivateKeySerializer);

        CreateSharesOperationContext createSharesOperationContextElGamal =
            new CreateSharesOperationContext(SharesType.ELECTORAL_BOARD);

        _createSharesHandlerElGamal = new CreateSharesHandler(createSharesOperationContextElGamal,
            elGamalKeyPairGenerator, privateKeySharesServiceElGamal, _smartcardService);

        BigInteger secretDataAsBigInt = new BigInteger(
            "7258566511205731274501076138955406501715314724101449712738362598579950386938540944887465860162123977709368083214223863972240202955142810928232980282975330370871308568085165077684085039876458285847574465974899160677249191425067809102830948294961345125633673938188727481916439054198157529653719706676063604935245024842269315556089246440255475308539173655418342524924565992764033784503570777559750521406592756973794259338361114583387726019915204757536641131719120008672199435739477691816523107124831533391498441341092740110948060646843197200688130802580784263720897279045752642595996003690127352386971518744986787935681");

        _secretDataAsGroupElement = new ZpGroupElement(secretDataAsBigInt, group);

        // initialise for RSA (Admin Board)

        RSAKeyPairGenerator rsaKeyPairGenerator = new RSAKeyPairGenerator(_asymmetricServiceAPI);

        RSAPrivateKeySerializer rsaPrivateKeySerializer = new RSAPrivateKeySerializer();

        PrivateKeySharesService privateKeySharesServiceRSA = new PrivateKeySharesService(rsaPrivateKeySerializer);

        CreateSharesOperationContext createSharesOperationContextRSA =
            new CreateSharesOperationContext(SharesType.ADMIN_BOARD);

        _createSharesHandlerRSA = new CreateSharesHandler(createSharesOperationContextRSA, rsaKeyPairGenerator,
            privateKeySharesServiceRSA, _smartcardService);

        _subject = new X500Principal("C=COUNTRY, O=ORGANIZATION, OU=ORGANIZATIONAL_UNIT, CN=COMMON_NAME");
    }

    @Test
    public void ElGamalwhenSplitWriteReadThenOk() throws SharesException, IOException, GeneralCryptoLibException {

        System.out.println("\n=============================================================================");
        System.out.println("Test of the generation of config for the Electoral Authority (ElGamal Keys)");
        System.out.println("=============================================================================\n");
        System.out.println("Press any key to continue...");

        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

        in.readLine();

        int n = 2;
        int threshold = 2;

        // //////////////////////////////////////////////////////
        //
        // Create shares
        //
        // //////////////////////////////////////////////////////

        String errorMsg = "Expected that there would not be any shares in memory";
        assertFalse(errorMsg, _createSharesHandlerElGamal.isSharesInMemory());

        // generate the keypair, and store it in shares in memory
        System.out.println("About to create a keypair and split the private key into shares...");
        _createSharesHandlerElGamal.generateAndSplit(n, threshold);
        System.out.println("Created a keypair and split the private key into shares");
        System.out.println();

        PublicKey publicKey = _createSharesHandlerElGamal.getPublicKey();
        ElGamalPublicKey elGamalPublicKey = null;
        if (publicKey instanceof ElGamalPublicKeyAdapter) {
            ElGamalPublicKeyAdapter elGamalPublicKeyAdapter = (ElGamalPublicKeyAdapter) publicKey;
            elGamalPublicKey = elGamalPublicKeyAdapter.getPublicKey();
        }

        CryptoAPIElGamalEncrypter encrypter = _elgamalServiceAPI.createEncrypter(elGamalPublicKey);
        List<ZpGroupElement> plaintext = new ArrayList<ZpGroupElement>();
        plaintext.add(_secretDataAsGroupElement);
        ElGamalEncrypterValues elGamalEncrypterValues = encrypter.encryptGroupElements(plaintext);

        errorMsg = "Expected that there would be some shares in memory";
        assertTrue(errorMsg, _createSharesHandlerElGamal.isSharesInMemory());

        // //////////////////////////////////////////////////////
        //
        // Write shares to smartcards
        //
        // //////////////////////////////////////////////////////

        System.out.println("Starting the process of writing to smartcards. Will write to " + n + " smartcards.");

        for (int i = 0; i < n; i++) {

            int indexToDisplay = i + 1;
            System.out.println("Please insert smartcard " + indexToDisplay + " and press Enter");
            in.readLine();

            String name = "name_" + i;
            _createSharesHandlerElGamal.writeShare(i, name, oldPinPuk, newPinPuk, _signAuthorityKeyPair.getPrivate(),
                _finalRunnable);
        }

        System.out.println("Finished writing shares");

        PublicKey electoralBoardPublicKey = _createSharesHandlerElGamal.getPublicKey();

        System.out.println("===================================================\n");
        System.out.println("This represents the period of time before the reconstruction of the private key...\n");
        System.out.println("Press any key to go to the moment of reconstructing the private key...\n");
        System.out.println("===================================================\n");
        in.readLine();

        // //////////////////////////////////////////////////////
        //
        // Reconstruct the shares
        //
        // //////////////////////////////////////////////////////

        System.out.println("Starting the process of reading shares...");

        ReadSharesOperationContext readSharesOperationContext = new ReadSharesOperationContext(
            SharesType.ELECTORAL_BOARD, _signAuthorityKeyPair.getPublic(), electoralBoardPublicKey);

        PrivateKeySerializer elGamalPrivateKeySerializer = new ElGamalPrivateKeySerializer();

        _readSharesHandler = new ReadSharesHandler(readSharesOperationContext, elGamalPrivateKeySerializer,
            _smartcardService, _asymmetricServiceAPI);

        for (int i = 0; i < n; i++) {

            int indexToDisplay = i + 1;
            System.out.println("Please insert smartcard " + indexToDisplay + " and press Enter");
            in.readLine();

            _readSharesHandler.readShare(newPinPuk);
        }
        PrivateKey reconstructedPrivateKey = _readSharesHandler.getPrivateKey();

        errorMsg = "The format of the reconstructed pricate key is not what was expected";
        assertEquals(errorMsg, "SCYTL_EL_GAMAL", reconstructedPrivateKey.getAlgorithm());

        ElGamalPrivateKey elGamalPrivateKey = null;
        if (reconstructedPrivateKey instanceof ElGamalPrivateKeyAdapter) {
            ElGamalPrivateKeyAdapter elGamalPrivateKeyAdapter = (ElGamalPrivateKeyAdapter) reconstructedPrivateKey;
            elGamalPrivateKey = elGamalPrivateKeyAdapter.getPrivateKey();
        }
        CryptoAPIElGamalDecrypter decrypter = _elgamalServiceAPI.createDecrypter(elGamalPrivateKey);

        List<ZpGroupElement> decrypted = decrypter.decrypt(elGamalEncrypterValues.getComputationValues(), false);

        ZpGroupElement decryptedData = decrypted.get(0);
        System.out.println("original data: " + _secretDataAsGroupElement.getValue());
        System.out.println("decrypted data: " + decryptedData.getValue());
        errorMsg = "The decrypted data does not match the original data";
        assertEquals(errorMsg, _secretDataAsGroupElement, decryptedData);

        if (decryptedData.getValue().equals(_secretDataAsGroupElement.getValue())) {
            System.out.println("SUCCESS - the decrypted data matches the original data");
        } else {
            System.out.println("ERROR - the decrypted data does not match the original data");
        }

    }

    @Test
    public void RSAwhenSplitWriteReadThenOk()
            throws SharesException, IOException, GeneralCryptoLibException, InvalidKeyException,
            NoSuchAlgorithmException {

        System.out.println("\n=============================================================================");
        System.out.println("Test of the generation of config for the Administration Board (RSA Keys)");
        System.out.println("=============================================================================\n");

        int n = 2;
        int threshold = 2;

        // //////////////////////////////////////////////////////
        //
        // Create shares
        //
        // //////////////////////////////////////////////////////

        String errorMsg = "Expected that there would not be any shares in memory";
        assertFalse(errorMsg, _createSharesHandlerRSA.isSharesInMemory());

        // generate the keypair, and store it in shares in memory
        System.out.println("About to create a keypair and split the private key into shares");
        _createSharesHandlerRSA.generateAndSplit(n, threshold);
        System.out.println("Created a keypair and split the private key into shares");
        System.out.println();

        errorMsg = "Expected that there would be some shares in memory";
        assertTrue(errorMsg, _createSharesHandlerRSA.isSharesInMemory());

        byte[] ciphertext =
            _asymmetricServiceAPI.encrypt(_createSharesHandlerRSA.getPublicKey(), _secretDataAsString.getBytes());

        // //////////////////////////////////////////////////////
        //
        // Write shares to smartcards
        //
        // //////////////////////////////////////////////////////

        System.out.println("Starting the process of writing to smartcards. Will write to " + n + " smartcards.");

        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        for (int i = 0; i < n; i++) {

            int indexToDisplay = i + 1;
            System.out.println("Please insert smartcard " + indexToDisplay + " and press Enter");
            in.readLine();

            String name = "name_" + i;
            _createSharesHandlerRSA.writeShareAndSelfSign(i, name, oldPinPuk, newPinPuk, _finalRunnable);
        }

        System.out.println("Finished writing shares\n");

        // //////////////////////////////////////////////////////
        //
        // Create CSR
        //
        // //////////////////////////////////////////////////////

        System.out.println("About to generate the CSR...\n");

        PKCS10CertificationRequest csr = _createSharesHandlerRSA.generateCSR(_subject, _csrGenerator);
        String csrAsPem = PemUtils.csrToPem(csr);

        System.out.println("The generated CSR is:");
        System.out.println(csrAsPem);

        // at this point the CRS is stored in a PEM, and all keys have been
        // removed from memory.

        // //////////////////////////////////////////////////////
        //
        // Read CSR and obtain public key
        //
        // //////////////////////////////////////////////////////

        errorMsg = "CSR PEM didnt contain expected string";
        assertTrue(errorMsg, csrAsPem.contains(Constants.CSR_BEGIN_STRING));
        assertTrue(errorMsg, csrAsPem.contains(Constants.CSR_END_STRING));

        JcaPKCS10CertificationRequest csrFromPEM = PemUtils.csrFromPem(csrAsPem);
        PublicKey publicKeyFromCsrPem = csrFromPEM.getPublicKey();

        System.out.println("===================================================\n");
        System.out.println("This represents the period of time before the reconstruction of the private key...\n");
        System.out.println("Press any key to go to the moment of reconstructing the private key...\n");
        System.out.println("===================================================\n");
        in.readLine();

        // //////////////////////////////////////////////////////
        //
        // Reconstruct the shares
        //
        // //////////////////////////////////////////////////////

        System.out.println("Starting the process of reading shares...");

        PublicKey adminBoardPublicKey = publicKeyFromCsrPem;

        ReadSharesOperationContext readSharesOperationContext =
            new ReadSharesOperationContext(SharesType.ADMIN_BOARD, adminBoardPublicKey, adminBoardPublicKey);

        PrivateKeySerializer rsaPrivateKeySerializer = new RSAPrivateKeySerializer();

        _readSharesHandler = new ReadSharesHandler(readSharesOperationContext, rsaPrivateKeySerializer,
            _smartcardService, _asymmetricServiceAPI);

        for (int i = 0; i < n; i++) {

            int indexToDisplay = i + 1;
            System.out.println("Please insert smartcard " + indexToDisplay + " and press Enter");
            in.readLine();

            _readSharesHandler.readShare(newPinPuk);
        }
        PrivateKey reconstructedPrivateKey = _readSharesHandler.getPrivateKey();

        errorMsg = "The format of the reconstructed pricate key is not what was expected";
        assertEquals(errorMsg, "RSA", reconstructedPrivateKey.getAlgorithm());

        byte[] plaintext = _asymmetricServiceAPI.decrypt(reconstructedPrivateKey, ciphertext);
        String recoveredString = new String(plaintext);

        errorMsg = "The recovered plaintext does not match the original plaintext";
        assertEquals(errorMsg, _secretDataAsString, recoveredString);

        System.out.println("original data: " + _secretDataAsString);
        System.out.println("decrypted data: " + recoveredString);
        if (_secretDataAsString.equals(recoveredString)) {
            System.out.println("SUCCESS - the decrypted data matches the original data");
        } else {
            System.out.println("ERROR - the decrypted data does not match the original data");
        }
    }

    @Test
    public void test_that_splits_and_statelessly_reconstructs_RSA_keys_when_keys_equal_to_the_threshold()
            throws SharesException, GeneralCryptoLibException, IOException, NoSuchAlgorithmException,
            InvalidKeyException, InterruptedException {

        int n = 2;
        int threshold = 2;

        splitAndStateLessRecoverRSAKeys(n, threshold);
    }

    @Test
    public void test_that_splits_and_statelessly_reconstructs_RSA_keys_when_more_keys_than_threshold()
            throws SharesException, GeneralCryptoLibException, IOException, NoSuchAlgorithmException,
            InvalidKeyException, InterruptedException {

        int n = 4;
        int threshold = 2;

        splitAndStateLessRecoverRSAKeys(n, threshold);
    }

    private void splitAndStateLessRecoverRSAKeys(final int n, final int threshold)
            throws SharesException, GeneralCryptoLibException, InterruptedException, IOException, InvalidKeyException,
            NoSuchAlgorithmException {
        System.out.println("\n=============================================================================");
        System.out.println("Test of the generation of config for the Administration Board (RSA Keys)");
        System.out.println("using the stateless shares reader");
        System.out.println("=============================================================================\n");
        System.out.println("Press any key to continue...");

        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        in.readLine();

        // //////////////////////////////////////////////////////
        //
        // Create shares
        //
        // //////////////////////////////////////////////////////

        String errorMsg = "Expected that there would not be any shares in memory";
        assertFalse(errorMsg, _createSharesHandlerRSA.isSharesInMemory());

        // generate the keypair, and store it in shares in memory
        System.out.println("About to create a keypair and split the private key into shares");
        _createSharesHandlerRSA.generateAndSplit(n, threshold);
        System.out.println("Created a keypair and split the private key into shares");
        System.out.println();

        errorMsg = "Expected that there would be some shares in memory";
        assertTrue(errorMsg, _createSharesHandlerRSA.isSharesInMemory());

        byte[] ciphertext =
            _asymmetricServiceAPI.encrypt(_createSharesHandlerRSA.getPublicKey(), _secretDataAsString.getBytes());

        // //////////////////////////////////////////////////////
        //
        // Write shares to smartcards
        //
        // //////////////////////////////////////////////////////

        System.out.println("Starting the process of writing to smartcards. Will write to " + n + " smartcards.");

        for (int i = 0; i < n; i++) {

            int indexToDisplay = i + 1;

            System.out.println("Please insert smartcard " + indexToDisplay + " and press Enter");
            in.readLine();

            String name = "name_" + i;
            _createSharesHandlerRSA.writeShareAndSelfSign(i, name, oldPinPuk, newPinPuk, _finalRunnable);
        }

        System.out.println("Finished writing shares\n");

        // //////////////////////////////////////////////////////
        //
        // Create CSR
        //
        // //////////////////////////////////////////////////////

        System.out.println("About to generate the CSR...\n");

        PKCS10CertificationRequest csr = _createSharesHandlerRSA.generateCSR(_subject, _csrGenerator);
        String csrAsPem = PemUtils.csrToPem(csr);

        System.out.println("The generated CSR is:");
        System.out.println(csrAsPem);

        // at this point the CRS is stored in a PEM, and all keys have been
        // removed from memory.

        // //////////////////////////////////////////////////////
        //
        // Read CSR and obtain public key
        //
        // //////////////////////////////////////////////////////

        errorMsg = "CSR PEM didnt contain expected string";
        assertTrue(errorMsg, csrAsPem.contains(Constants.CSR_BEGIN_STRING));
        assertTrue(errorMsg, csrAsPem.contains(Constants.CSR_END_STRING));

        JcaPKCS10CertificationRequest csrFromPEM = PemUtils.csrFromPem(csrAsPem);
        PublicKey publicKeyFromCsrPem = csrFromPEM.getPublicKey();

        System.out.println("===================================================\n");
        System.out.println("This represents the period of time before the reconstruction of the private key...\n");
        System.out.println("Press any key to go to the moment of reconstructing the private key...\n");
        System.out.println("===================================================\n");
        in.readLine();

        // //////////////////////////////////////////////////////
        //
        // Reconstruct the shares
        //
        // //////////////////////////////////////////////////////

        System.out
            .println("Starting the process of reading the minimum number (" + threshold + ") of required shares...");

        PublicKey adminBoardPublicKey = publicKeyFromCsrPem;

        PrivateKeySerializer rsaPrivateKeySerializer = new RSAPrivateKeySerializer();

        _statelessReadSharesHandler = new StatelessReadSharesHandler(rsaPrivateKeySerializer, _smartcardService,
            _asymmetricServiceAPI, _elgamalServiceAPI);

        Set<String> serializedShares = new HashSet<>();
        for (int i = 0; i < threshold; i++) {

            int indexToDisplay = i + 1;
            System.out.println("Please insert smartcard " + indexToDisplay + " and press Enter");
            in.readLine();

            String serializedShare = _statelessReadSharesHandler.readShareAndStringify(newPinPuk, adminBoardPublicKey);
            serializedShares.add(serializedShare);
        }
        PrivateKey reconstructedPrivateKey =
            _statelessReadSharesHandler.getPrivateKeyWithSerializedShares(serializedShares, adminBoardPublicKey);

        errorMsg = "The format of the reconstructed pricate key is not what was expected";
        assertEquals(errorMsg, "RSA", reconstructedPrivateKey.getAlgorithm());

        byte[] plaintext = _asymmetricServiceAPI.decrypt(reconstructedPrivateKey, ciphertext);
        String recoveredString = new String(plaintext);

        errorMsg = "The recovered plaintext does not match the original plaintext";
        assertEquals(errorMsg, _secretDataAsString, recoveredString);

        System.out.println("original data: " + _secretDataAsString);
        System.out.println("decrypted data: " + recoveredString);
        if (_secretDataAsString.equals(recoveredString)) {
            System.out.println("SUCCESS - the decrypted data matches the original data");
        } else {
            System.out.println("ERROR - the decrypted data does not match the original data");
        }
    }

    @Test
    public void test_that_splits_and_statelessly_reconstructs_ElGamal_keys_when_keys_equal_to_the_threshold()
            throws SharesException, GeneralCryptoLibException, IOException, NoSuchAlgorithmException,
            InvalidKeyException, InterruptedException {

        int n = 2;
        int threshold = 2;

        splitAndStatelessRecoverElGamalKeys(n, threshold);

    }

    @Test
    public void test_that_splits_and_statelessly_reconstructs_ElGamal_keys_when_more_keys_than_threshold()
            throws SharesException, GeneralCryptoLibException, IOException, NoSuchAlgorithmException,
            InvalidKeyException, InterruptedException {

        int n = 4;
        int threshold = 2;

        splitAndStatelessRecoverElGamalKeys(n, threshold);

    }

    private void splitAndStatelessRecoverElGamalKeys(final int n, final int threshold)
            throws SharesException, GeneralCryptoLibException, InterruptedException, IOException {
        System.out.println("\n=============================================================================");
        System.out.println("Test of the generation of config for the Electoral Authority (ElGamal Keys)");
        System.out.println("using the stateless shares reader");
        System.out.println("=============================================================================\n");

        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

        // //////////////////////////////////////////////////////
        //
        // Create shares
        //
        // //////////////////////////////////////////////////////

        String errorMsg = "Expected that there would not be any shares in memory";
        assertFalse(errorMsg, _createSharesHandlerElGamal.isSharesInMemory());

        // generate the keypair, and store it in shares in memory
        System.out.println("About to create a keypair and split the private key into shares...");
        _createSharesHandlerElGamal.generateAndSplit(n, threshold);
        System.out.println("Created a keypair and split the private key into shares");
        System.out.println();

        PublicKey publicKey = _createSharesHandlerElGamal.getPublicKey();
        ElGamalPublicKey elGamalPublicKey = null;
        if (publicKey instanceof ElGamalPublicKeyAdapter) {
            ElGamalPublicKeyAdapter elGamalPublicKeyAdapter = (ElGamalPublicKeyAdapter) publicKey;
            elGamalPublicKey = elGamalPublicKeyAdapter.getPublicKey();
        }

        CryptoAPIElGamalEncrypter encrypter = _elgamalServiceAPI.createEncrypter(elGamalPublicKey);
        List<ZpGroupElement> plaintext = new ArrayList<ZpGroupElement>();
        plaintext.add(_secretDataAsGroupElement);
        ElGamalEncrypterValues elGamalEncrypterValues = encrypter.encryptGroupElements(plaintext);

        errorMsg = "Expected that there would be some shares in memory";
        assertTrue(errorMsg, _createSharesHandlerElGamal.isSharesInMemory());

        // //////////////////////////////////////////////////////
        //
        // Write shares to smartcards
        //
        // //////////////////////////////////////////////////////

        System.out.println("Starting the process of writing to smartcards. Will write to " + n + " smartcards.");

        for (int i = 0; i < n; i++) {

            int indexToDisplay = i + 1;
            System.out.println("Please insert smartcard " + indexToDisplay + " and press Enter");
            in.readLine();

            String name = "name_" + i;
            _createSharesHandlerElGamal.writeShare(i, name, oldPinPuk, newPinPuk, _signAuthorityKeyPair.getPrivate(),
                _finalRunnable);
        }

        System.out.println("Finished writing shares");

        PublicKey electoralBoardPublicKey = _createSharesHandlerElGamal.getPublicKey();

        System.out.println("===================================================\n");
        System.out.println("This represents the period of time before the reconstruction of the private key...\n");
        System.out.println("Press any key to go to the moment of reconstructing the private key...\n");
        System.out.println("===================================================\n");
        in.readLine();

        // //////////////////////////////////////////////////////
        //
        // Reconstruct the shares
        //
        // //////////////////////////////////////////////////////

        System.out.println("Starting the process of reading shares...");

        PublicKey issuerPublicKey = _signAuthorityKeyPair.getPublic();

        PrivateKeySerializer elGamalPrivateKeySerializer = new ElGamalPrivateKeySerializer();

        _statelessReadSharesHandler = new StatelessReadSharesHandler(elGamalPrivateKeySerializer, _smartcardService,
            _asymmetricServiceAPI, _elgamalServiceAPI);

        Set<String> serializedShares = new HashSet<>();
        for (int i = 0; i < threshold; i++) {

            int indexToDisplay = i + 1;
            System.out.println("Please insert smartcard " + indexToDisplay + " and press Enter");
            in.readLine();

            String serializedShare = _statelessReadSharesHandler.readShareAndStringify(newPinPuk, issuerPublicKey);
            serializedShares.add(serializedShare);
        }
        PrivateKey reconstructedPrivateKey =
            _statelessReadSharesHandler.getPrivateKeyWithSerializedShares(serializedShares, electoralBoardPublicKey);

        errorMsg = "The format of the reconstructed pricate key is not what was expected";
        assertEquals(errorMsg, "SCYTL_EL_GAMAL", reconstructedPrivateKey.getAlgorithm());

        ElGamalPrivateKey elGamalPrivateKey = null;
        if (reconstructedPrivateKey instanceof ElGamalPrivateKeyAdapter) {
            ElGamalPrivateKeyAdapter elGamalPrivateKeyAdapter = (ElGamalPrivateKeyAdapter) reconstructedPrivateKey;
            elGamalPrivateKey = elGamalPrivateKeyAdapter.getPrivateKey();
        }
        CryptoAPIElGamalDecrypter decrypter = _elgamalServiceAPI.createDecrypter(elGamalPrivateKey);

        List<ZpGroupElement> decrypted = decrypter.decrypt(elGamalEncrypterValues.getComputationValues(), false);

        ZpGroupElement decryptedData = decrypted.get(0);
        System.out.println("original data: " + _secretDataAsGroupElement.getValue());
        System.out.println("decrypted data: " + decryptedData.getValue());
        errorMsg = "The decrypted data does not match the original data";
        assertEquals(errorMsg, _secretDataAsGroupElement, decryptedData);

        if (decryptedData.getValue().equals(_secretDataAsGroupElement.getValue())) {
            System.out.println("SUCCESS - the decrypted data matches the original data");
        } else {
            System.out.println("ERROR - the decrypted data does not match the original data");
        }
    }
}
