/*
 * 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.vv.domain.service;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.api.primitives.PrimitivesServiceAPI;
import com.scytl.cryptolib.api.proofs.ProofsServiceAPI;
import com.scytl.cryptolib.derivation.cryptoapi.CryptoAPIKDFDeriver;
import com.scytl.cryptolib.derivation.cryptoapi.CryptoAPIPBKDFDeriver;
import com.scytl.cryptolib.elgamal.bean.ElGamalPrivateKey;
import com.scytl.cryptolib.elgamal.bean.ElGamalPublicKey;
import com.scytl.cryptolib.ffc.cryptoapi.FFCEngine;
import com.scytl.cryptolib.mathematical.groups.MathematicalGroup;
import com.scytl.cryptolib.mathematical.groups.impl.Exponent;
import com.scytl.cryptolib.mathematical.groups.impl.ZpGroupElement;
import com.scytl.cryptolib.primitives.service.PrimitivesService;
import com.scytl.cryptolib.proofs.cryptoapi.ProofPreComputerAPI;
import com.scytl.cryptolib.proofs.cryptoapi.ProofProverAPI;
import com.scytl.cryptolib.proofs.cryptoapi.ProofVerifierAPI;
import com.scytl.cryptolib.proofs.service.ProofsService;
import com.scytl.cryptolib.securerandom.cryptoapi.CryptoAPIRandomBytes;
import com.scytl.cryptolib.securerandom.cryptoapi.CryptoAPIRandomInteger;
import com.scytl.cryptolib.securerandom.cryptoapi.CryptoAPIRandomString;
import com.scytl.products.oscore.logging.api.writer.SecureLoggingWriter;
import com.scytl.products.ov.commons.beans.exceptions.ResourceNotFoundException;
import com.scytl.products.ov.commons.dto.ChoiceCodesVerificationResPayload;
import com.scytl.products.ov.commons.tracking.TrackIdInstance;
import com.scytl.products.ov.commons.ui.ws.rs.ObjectMappers;
import com.scytl.products.ov.commons.validation.ValidationResult;
import com.scytl.products.ov.vv.domain.model.verification.Verification;
import com.scytl.products.ov.vv.domain.model.verification.VerificationDerivedKeys;
import com.scytl.products.ov.vv.domain.model.verification.VerificationDerivedKeysRepository;
import com.scytl.products.ov.vv.domain.model.verification.VerificationRepository;
import com.scytl.products.ov.vv.domain.model.verificationset.VerificationSetData;
import com.scytl.products.ov.vv.domain.model.verificationset.VerificationSetEntity;
import com.scytl.products.ov.vv.domain.model.verificationset.VerificationSetRepository;

@RunWith(MockitoJUnitRunner.class)
public class ValidationServiceImplTest {

    private static String privateKeyJson =
        "{\"privateKey\":{\"zpSubgroup\":{\"g\":\"Ag==\",\"p\":\"AIGt8wzizyJ8+ia716YgY4FjG5gJRjfz+iB3OeWEMLCOLnoWG0wHwFQyo2VNx4lgBPDmSIb5NtJ/12U3dfe7JdgYjEMSaz9HUrfowHNwCuD1VVmDXxUvyxPSV5/iss7a85sopvR9n09imvvp8XDfPNuccd10TQwGSKVt2vN7vSMrgQOsGEuBBsEmsdlRil4+x9NTYphsc/7vTV9pRN4U+ZI5yTbBof6SFcH5mwAatlZB/RztwA1wh3qUUgyLz/kTKqTRbaPc5W1+LDPBZnMiaTWq786418pio4oM86mVIthfE/34S0kn8GpJGzNF12NzbVQzbpv6YpmrPKG1FfaQPwc=\",\"q\":\"QNb5hnFnkT59E13r0xAxwLGNzASjG/n9EDuc8sIYWEcXPQsNpgPgKhlRsqbjxLACeHMkQ3ybaT/rspu6+92S7AxGIYk1n6OpW/RgObgFcHqqrMGvipfliekrz/FZZ215zZRTej7Pp7FNffT4uG+ebc447romhgMkUrbteb3ekZXAgdYMJcCDYJNY7KjFLx9j6amxTDY5/3emr7Sibwp8yRzkm2DQ/0kK4PzNgA1bKyD+jnbgBrhDvUopBkXn/ImVUmi20e5ytr8WGeCzOZE0mtV351xr5TFRxQZ51MqRbC+J/vwlpJP4NSSNmaLrsbm2qhm3Tf0xTNWeUNqK+0gfgw==\"},\"exponents\":[\"EIvIOYQSn7wlPv4pr2XUWNw07hcu68kPK7wcREa/w3UcPVQWddWceACCZaQkfPIZt4xuYHIRsxqBSDgDXm+gRD9rvlbIUO92xR1JAenVaoAikN/XFiZ/ZH56umgEMr3SQGZ/hyvNIY7IHPPuIN3vt2ppWNbuupITBKyl7y2hmHWwT6jzV17EBcLvu185hntcRUZjPgP9MvFtztu2pCRMUFqKI1GjFme+F+zbOXdpifPlHdrg4z9hDFyTBM/PvkQRTKijns8AmIFDnCoHkbkBdWUFr+bzNNp9XYF63BD7aSvBCT1TsWdcwgi/twBp83DCfkdGGqryECInzsXEMTsWVw==\"]}}\n";

    @Mock
    private VerificationRepository verificationRepository;

    @Mock
    private VerificationSetRepository verificationSetRepositoryMock;

    @Mock
    private VerificationDerivedKeysRepository verificationDerivedKeysRepositoryMock;

    @Mock
    private TrackIdInstance trackId;

    @Mock
    private SecureLoggingWriter loggingWriter;
    
    @Spy
    private PrimitivesServiceAPI primitivesService = new PrimitivesServiceForTest();

    @Spy
    private ProofsServiceAPI proofsServices = new ProofsServiceForTest();

    @InjectMocks
    ValidationServiceImpl sut = new ValidationServiceImpl();

    @Test
    public void testChoiceCodeValidateProofSuccessful()
            throws JsonParseException, JsonMappingException, GeneralCryptoLibException, ResourceNotFoundException,
            IOException {
        // Prepare
        // (data preparation logic can be found in method ExponentiationServiceImpl.exponentiateCleartexts)
        String tenantId = "tenantId";
        String electionEventId = "electionEventId";
        String verificationCardId = "verificationCardId";
        String verificationCardSetId = "verificationCardSetId";

        Verification verification = new Verification();
        verification.setVerificationCardSetId(verificationCardSetId);

        Mockito.when(verificationRepository.findByTenantIdElectionEventIdVerificationCardId(tenantId, electionEventId,
            verificationCardId)).thenReturn(verification);

        ElGamalPrivateKey egPrivateKey = ElGamalPrivateKey.fromJson(privateKeyJson);

        Exponent egDerivedPrivateKey = Exponent.fromJson(
            "{\"exponent\":{\"q\":\"QNb5hnFnkT59E13r0xAxwLGNzASjG/n9EDuc8sIYWEcXPQsNpgPgKhlRsqbjxLACeHMkQ3ybaT/rspu6+92S7AxGIYk1n6OpW/RgObgFcHqqrMGvipfliekrz/FZZ215zZRTej7Pp7FNffT4uG+ebc447romhgMkUrbteb3ekZXAgdYMJcCDYJNY7KjFLx9j6amxTDY5/3emr7Sibwp8yRzkm2DQ/0kK4PzNgA1bKyD+jnbgBrhDvUopBkXn/ImVUmi20e5ytr8WGeCzOZE0mtV351xr5TFRxQZ51MqRbC+J/vwlpJP4NSSNmaLrsbm2qhm3Tf0xTNWeUNqK+0gfgw==\",\"value\":\"NFveu+RorkwyZjPmm09mlRWVPt2HiYZUD/WXEP4PnFX8fg7hdI3/k8eR89iAoWSKEW0C7XR1AtB0NZEyxbvTkH9mYQXaU3Y3fzHgsjmzTbqcLJ+8yWknxbMOvr3eEq4RwNe5Izy7tvM10LpRTxa//KYk6sNmonikbVM4lUk4JyF7zwciYH1JYWB2nhKHljfF6qICv5DgEafOp2rte1xfiVrUD5X0afZkZohmeRoZTgftQKotnplcPJuZeH0gRTk8lXFE/0wbt1RtNPk6ALznlPplbs4DdP4u8ogiibof1MuKKb3onxfN5N8RtDcprX7cFcRytUd/Hk47o3q83LWDWQ==\"}}");

        ElGamalPublicKey egPublicKey = ElGamalPublicKey.fromJson(
            "{\"publicKey\":{\"zpSubgroup\":{\"g\":\"Ag==\",\"p\":\"AIGt8wzizyJ8+ia716YgY4FjG5gJRjfz+iB3OeWEMLCOLnoWG0wHwFQyo2VNx4lgBPDmSIb5NtJ/12U3dfe7JdgYjEMSaz9HUrfowHNwCuD1VVmDXxUvyxPSV5/iss7a85sopvR9n09imvvp8XDfPNuccd10TQwGSKVt2vN7vSMrgQOsGEuBBsEmsdlRil4+x9NTYphsc/7vTV9pRN4U+ZI5yTbBof6SFcH5mwAatlZB/RztwA1wh3qUUgyLz/kTKqTRbaPc5W1+LDPBZnMiaTWq786418pio4oM86mVIthfE/34S0kn8GpJGzNF12NzbVQzbpv6YpmrPKG1FfaQPwc=\",\"q\":\"QNb5hnFnkT59E13r0xAxwLGNzASjG/n9EDuc8sIYWEcXPQsNpgPgKhlRsqbjxLACeHMkQ3ybaT/rspu6+92S7AxGIYk1n6OpW/RgObgFcHqqrMGvipfliekrz/FZZ215zZRTej7Pp7FNffT4uG+ebc447romhgMkUrbteb3ekZXAgdYMJcCDYJNY7KjFLx9j6amxTDY5/3emr7Sibwp8yRzkm2DQ/0kK4PzNgA1bKyD+jnbgBrhDvUopBkXn/ImVUmi20e5ytr8WGeCzOZE0mtV351xr5TFRxQZ51MqRbC+J/vwlpJP4NSSNmaLrsbm2qhm3Tf0xTNWeUNqK+0gfgw==\"},\"elements\":[\"cDEirCGORSF6n3eWy0zxrjASTNBes9xCXLEJL9AigcUtAEauqgzQEOE+r2SE1eIXHbf43Iv4RT1ffKD6msPbKOppmB6bdxOtlzHK/6HJxJ9z6zNa75OTB2NbRCxGLhuN92joFFN8uQqiZ87PBYE4ZmA347gjVSvJsolsoFki2r4gLRITFbo/0CYFZqUXUgIzbyTX7DYIjCWY9o4sXofd1Ay3QgdHvN6HEVpdUc62bMxu1q9wZopPFdA03fadGyKeQkT93EgIAcCSuMemfgNEcONEieMSg3pLIKy68dKfxIP7y09WdY4f+to6d+xJVU/61/Gp41yPNS9n9BruQgsdAA==\"]}}");

        ZpGroupElement egDerivedPublicKey = egPrivateKey.getGroup().getGenerator().exponentiate(egDerivedPrivateKey);

        VerificationSetEntity verificationSet = new VerificationSetEntity();
        VerificationSetData verificationSetData = new VerificationSetData();
        verificationSetData.setChoicesCodesEncryptionPublicKey(
            Base64.getEncoder().encodeToString(egPublicKey.toJson().getBytes(StandardCharsets.UTF_8)));
        verificationSet.setJSON(ObjectMappers.toJson(verificationSetData));

        Mockito.when(verificationSetRepositoryMock.findByTenantIdElectionEventIdVerificationCardSetId(tenantId,
            electionEventId, verificationCardSetId)).thenReturn(verificationSet);

        VerificationDerivedKeys verificationDerivedKeys = new VerificationDerivedKeys();
        verificationDerivedKeys.setCcodeDerivedKeyCommitment(
            ObjectMappers.toJson(Arrays.asList(egDerivedPublicKey.toJson().getBytes(StandardCharsets.UTF_8))));

        Mockito.when(verificationDerivedKeysRepositoryMock.findByTenantIdElectionEventIdVerificationCardId(tenantId,
            electionEventId, verificationCardId)).thenReturn(verificationDerivedKeys);

        ChoiceCodesVerificationResPayload choiceCodesComputationResult = new ChoiceCodesVerificationResPayload();
        choiceCodesComputationResult.setChoiceCodesDerivedKeyJson(
            Base64.getEncoder().encodeToString(egDerivedPublicKey.toJson().getBytes(StandardCharsets.UTF_8)));
        choiceCodesComputationResult.setExponentiationProofJson(
            "{\"zkProof\":{\"hash\":\"CKLyn4/YGshibkYQhO1dpizl0kR7FZX/7YCipDWPess=\",\"values\":[\"D0yWfNkqbxwkseE9sMEus7tguLYwkoOlPYPrhHH+EEDLqEMY11q1nfYwjQohPFuWfJDdqklPstCPBACGyATioRsZ4wPFmWUwY14I4JOgI1ujjErZD+y/NzwJK9dmHV4ZgoN8zNoy23gJZD+UhvJ6cByLAHXuRdZ9Wo+eZNEa1eVidHXukoWuYS00kan3qDLK/W9kWD4xP9Ncg39fsSjbck/e0HibU/0M1BngejqOPvesktGQecmP2k6LSdMyhB80pHReS3fUGmz9Lljul77EIbe2JGuaUTwLR0j6lv2E7Ju7SmKoRTszgvMJXlY6POV2CTuybhFyEGbLL+7yN96VJA==\"],\"q\":\"QNb5hnFnkT59E13r0xAxwLGNzASjG/n9EDuc8sIYWEcXPQsNpgPgKhlRsqbjxLACeHMkQ3ybaT/rspu6+92S7AxGIYk1n6OpW/RgObgFcHqqrMGvipfliekrz/FZZ215zZRTej7Pp7FNffT4uG+ebc447romhgMkUrbteb3ekZXAgdYMJcCDYJNY7KjFLx9j6amxTDY5/3emr7Sibwp8yRzkm2DQ/0kK4PzNgA1bKyD+jnbgBrhDvUopBkXn/ImVUmi20e5ytr8WGeCzOZE0mtV351xr5TFRxQZ51MqRbC+J/vwlpJP4NSSNmaLrsbm2qhm3Tf0xTNWeUNqK+0gfgw==\"}}");
        Map<BigInteger, BigInteger> primeToComputedPrime = new LinkedHashMap<>();
        primeToComputedPrime.put(new BigInteger("1"), new BigInteger("1"));
        primeToComputedPrime.put(new BigInteger("2"), new BigInteger(
            "9743500770682842476578763122110513181015041388641391129587979585091986584053667712077859282164994506051791662477179281954384292018232206750641637370301848884047131951946532656427224042814761393950201122092429026290873081326371788767045908371074424846330875267177506860957434683891976591208529580185159757311304196715507907470476789468923898120564457550209646222450394607931573591570355754722347466734433631984554786965454460967960294717150070924703499407629582949830915336315747202854339397335805858049075501176684375331117831300229624521101824031137802343788454884996373285084029213412794175568101036159808682757285"));
        primeToComputedPrime.put(new BigInteger("3"), new BigInteger(
            "2087126522855024352687742139871443310692821898545387704987084591197090044040582060604884718390715412161828006367580749298261188249216766151443153563193323588271970841905970434807226164815498937346432389750579399411692160916231669285934418364693026720853081417022720583214541407876365561034755876125122094457515203575248000507573019554495634097933743875843499352953441325216490441873467492577866704691442941221719844242479425810717043975206130120144130817498519662061147597726886376000586347741760354024304310708571472065186778351503455078768138416476119611296330473765994112974461371143771320602851793646161725573197"));
        choiceCodesComputationResult.setPrimeToComputedPrime(primeToComputedPrime);

        // Execute
        ValidationResult validationResult =
            sut.validate(tenantId, electionEventId, verificationCardId, choiceCodesComputationResult);

        // Verify
        assertThat(validationResult.isResult(), is(true));
    }

    @Test
    public void testChoiceCodeValidateProofInvalid()
            throws JsonParseException, JsonMappingException, GeneralCryptoLibException, ResourceNotFoundException,
            IOException {
        // Prepare
        // (data preparation logic can be found in method ExponentiationServiceImpl.exponentiateCleartexts)
        String tenantId = "tenantId";
        String electionEventId = "electionEventId";
        String verificationCardId = "verificationCardId";
        String verificationCardSetId = "verificationCardSetId";

        Verification verification = new Verification();
        verification.setVerificationCardSetId(verificationCardSetId);

        Mockito.when(verificationRepository.findByTenantIdElectionEventIdVerificationCardId(tenantId, electionEventId,
            verificationCardId)).thenReturn(verification);
        Mockito.when(trackId.getTrackId()).thenReturn("trackid");

        ElGamalPrivateKey egPrivateKey = ElGamalPrivateKey.fromJson(privateKeyJson);

        Exponent egDerivedPrivateKey = Exponent.fromJson(
            "{\"exponent\":{\"q\":\"QNb5hnFnkT59E13r0xAxwLGNzASjG/n9EDuc8sIYWEcXPQsNpgPgKhlRsqbjxLACeHMkQ3ybaT/rspu6+92S7AxGIYk1n6OpW/RgObgFcHqqrMGvipfliekrz/FZZ215zZRTej7Pp7FNffT4uG+ebc447romhgMkUrbteb3ekZXAgdYMJcCDYJNY7KjFLx9j6amxTDY5/3emr7Sibwp8yRzkm2DQ/0kK4PzNgA1bKyD+jnbgBrhDvUopBkXn/ImVUmi20e5ytr8WGeCzOZE0mtV351xr5TFRxQZ51MqRbC+J/vwlpJP4NSSNmaLrsbm2qhm3Tf0xTNWeUNqK+0gfgw==\",\"value\":\"NFveu+RorkwyZjPmm09mlRWVPt2HiYZUD/WXEP4PnFX8fg7hdI3/k8eR89iAoWSKEW0C7XR1AtB0NZEyxbvTkH9mYQXaU3Y3fzHgsjmzTbqcLJ+8yWknxbMOvr3eEq4RwNe5Izy7tvM10LpRTxa//KYk6sNmonikbVM4lUk4JyF7zwciYH1JYWB2nhKHljfF6qICv5DgEafOp2rte1xfiVrUD5X0afZkZohmeRoZTgftQKotnplcPJuZeH0gRTk8lXFE/0wbt1RtNPk6ALznlPplbs4DdP4u8ogiibof1MuKKb3onxfN5N8RtDcprX7cFcRytUd/Hk47o3q83LWDWQ==\"}}");

        ElGamalPublicKey egPublicKey = ElGamalPublicKey.fromJson(
            "{\"publicKey\":{\"zpSubgroup\":{\"g\":\"Ag==\",\"p\":\"AIGt8wzizyJ8+ia716YgY4FjG5gJRjfz+iB3OeWEMLCOLnoWG0wHwFQyo2VNx4lgBPDmSIb5NtJ/12U3dfe7JdgYjEMSaz9HUrfowHNwCuD1VVmDXxUvyxPSV5/iss7a85sopvR9n09imvvp8XDfPNuccd10TQwGSKVt2vN7vSMrgQOsGEuBBsEmsdlRil4+x9NTYphsc/7vTV9pRN4U+ZI5yTbBof6SFcH5mwAatlZB/RztwA1wh3qUUgyLz/kTKqTRbaPc5W1+LDPBZnMiaTWq786418pio4oM86mVIthfE/34S0kn8GpJGzNF12NzbVQzbpv6YpmrPKG1FfaQPwc=\",\"q\":\"QNb5hnFnkT59E13r0xAxwLGNzASjG/n9EDuc8sIYWEcXPQsNpgPgKhlRsqbjxLACeHMkQ3ybaT/rspu6+92S7AxGIYk1n6OpW/RgObgFcHqqrMGvipfliekrz/FZZ215zZRTej7Pp7FNffT4uG+ebc447romhgMkUrbteb3ekZXAgdYMJcCDYJNY7KjFLx9j6amxTDY5/3emr7Sibwp8yRzkm2DQ/0kK4PzNgA1bKyD+jnbgBrhDvUopBkXn/ImVUmi20e5ytr8WGeCzOZE0mtV351xr5TFRxQZ51MqRbC+J/vwlpJP4NSSNmaLrsbm2qhm3Tf0xTNWeUNqK+0gfgw==\"},\"elements\":[\"cDEirCGORSF6n3eWy0zxrjASTNBes9xCXLEJL9AigcUtAEauqgzQEOE+r2SE1eIXHbf43Iv4RT1ffKD6msPbKOppmB6bdxOtlzHK/6HJxJ9z6zNa75OTB2NbRCxGLhuN92joFFN8uQqiZ87PBYE4ZmA347gjVSvJsolsoFki2r4gLRITFbo/0CYFZqUXUgIzbyTX7DYIjCWY9o4sXofd1Ay3QgdHvN6HEVpdUc62bMxu1q9wZopPFdA03fadGyKeQkT93EgIAcCSuMemfgNEcONEieMSg3pLIKy68dKfxIP7y09WdY4f+to6d+xJVU/61/Gp41yPNS9n9BruQgsdAA==\"]}}");

        ZpGroupElement egDerivedPublicKey = egPrivateKey.getGroup().getGenerator().exponentiate(egDerivedPrivateKey);

        VerificationSetEntity verificationSet = new VerificationSetEntity();
        VerificationSetData verificationSetData = new VerificationSetData();
        verificationSetData.setChoicesCodesEncryptionPublicKey(
            Base64.getEncoder().encodeToString(egPublicKey.toJson().getBytes(StandardCharsets.UTF_8)));
        verificationSet.setJSON(ObjectMappers.toJson(verificationSetData));

        Mockito.when(verificationSetRepositoryMock.findByTenantIdElectionEventIdVerificationCardSetId(tenantId,
            electionEventId, verificationCardSetId)).thenReturn(verificationSet);

        VerificationDerivedKeys verificationDerivedKeys = new VerificationDerivedKeys();
        verificationDerivedKeys.setCcodeDerivedKeyCommitment(
            ObjectMappers.toJson(Arrays.asList(egDerivedPublicKey.toJson().getBytes(StandardCharsets.UTF_8))));

        Mockito.when(verificationDerivedKeysRepositoryMock.findByTenantIdElectionEventIdVerificationCardId(tenantId,
            electionEventId, verificationCardId)).thenReturn(verificationDerivedKeys);

        ChoiceCodesVerificationResPayload choiceCodesComputationResult = new ChoiceCodesVerificationResPayload();
        choiceCodesComputationResult.setChoiceCodesDerivedKeyJson(
            Base64.getEncoder().encodeToString(egDerivedPublicKey.toJson().getBytes(StandardCharsets.UTF_8)));
        choiceCodesComputationResult.setExponentiationProofJson(
            "{\"zkProof\":{\"hash\":\"CKLyn4/YGshibkYQhO1dpizl0kR7FZX/7YCipDWPess=\",\"values\":[\"D0yWfNkqbxwkseE9sMEus7tguLYwkoOlPYPrhHH+EEDLqEMY11q1nfYwjQohPFuWfJDdqklPstCPBACGyATioRsZ4wPFmWUwY14I4JOgI1ujjErZD+y/NzwJK9dmHV4ZgoN8zNoy23gJZD+UhvJ6cByLAHXuRdZ9Wo+eZNEa1eVidHXukoWuYS00kan3qDLK/W9kWD4xP9Ncg39fsSjbck/e0HibU/0M1BngejqOPvesktGQecmP2k6LSdMyhB80pHReS3fUGmz9Lljul77EIbe2JGuaUTwLR0j6lv2E7Ju7SmKoRTszgvMJXlY6POV2CTuybhFyEGbLL+7yN96VJA==\"],\"q\":\"QNb5hnFnkT59E13r0xAxwLGNzASjG/n9EDuc8sIYWEcXPQsNpgPgKhlRsqbjxLACeHMkQ3ybaT/rspu6+92S7AxGIYk1n6OpW/RgObgFcHqqrMGvipfliekrz/FZZ215zZRTej7Pp7FNffT4uG+ebc447romhgMkUrbteb3ekZXAgdYMJcCDYJNY7KjFLx9j6amxTDY5/3emr7Sibwp8yRzkm2DQ/0kK4PzNgA1bKyD+jnbgBrhDvUopBkXn/ImVUmi20e5ytr8WGeCzOZE0mtV351xr5TFRxQZ51MqRbC+J/vwlpJP4NSSNmaLrsbm2qhm3Tf0xTNWeUNqK+0gfgw==\"}}");
        Map<BigInteger, BigInteger> primeToComputedPrime = new LinkedHashMap<>();
        primeToComputedPrime.put(new BigInteger("1"), new BigInteger("1"));
        primeToComputedPrime.put(new BigInteger("2"), new BigInteger("2")); // invalid prime on purpose
        primeToComputedPrime.put(new BigInteger("3"), new BigInteger(
            "2087126522855024352687742139871443310692821898545387704987084591197090044040582060604884718390715412161828006367580749298261188249216766151443153563193323588271970841905970434807226164815498937346432389750579399411692160916231669285934418364693026720853081417022720583214541407876365561034755876125122094457515203575248000507573019554495634097933743875843499352953441325216490441873467492577866704691442941221719844242479425810717043975206130120144130817498519662061147597726886376000586347741760354024304310708571472065186778351503455078768138416476119611296330473765994112974461371143771320602851793646161725573197"));
        choiceCodesComputationResult.setPrimeToComputedPrime(primeToComputedPrime);

        // Execute
        ValidationResult validationResult =
            sut.validate(tenantId, electionEventId, verificationCardId, choiceCodesComputationResult);

        // Verify
        assertThat(validationResult.isResult(), is(false));
    }
    
    @Test
    public void testCastCodeValidateProofSuccessful()
            throws JsonParseException, JsonMappingException, GeneralCryptoLibException, ResourceNotFoundException,
            IOException {
        // Prepare 
        // (data preparation logic can be found in method ExponentiationServiceImpl.exponentiateCleartexts + ValidationServiceImpl:181)
        String tenantId = "tenantId";
        String electionEventId = "electionEventId";
        String verificationCardId = "verificationCardId";
        String verificationCardSetId = "verificationCardSetId";

        Verification verification = new Verification();
        verification.setVerificationCardSetId(verificationCardSetId);

        Mockito.when(verificationRepository.findByTenantIdElectionEventIdVerificationCardId(tenantId, electionEventId,
            verificationCardId)).thenReturn(verification);

        ElGamalPrivateKey egPrivateKey = ElGamalPrivateKey.fromJson(privateKeyJson);
        
        Exponent egDerivedPrivateKey = new Exponent(egPrivateKey.getGroup().getQ(), egPrivateKey.getKeys().get(0).getValue());

        ElGamalPublicKey egPublicKey = ElGamalPublicKey.fromJson(
            "{\"publicKey\":{\"zpSubgroup\":{\"g\":\"Ag==\",\"p\":\"AIGt8wzizyJ8+ia716YgY4FjG5gJRjfz+iB3OeWEMLCOLnoWG0wHwFQyo2VNx4lgBPDmSIb5NtJ/12U3dfe7JdgYjEMSaz9HUrfowHNwCuD1VVmDXxUvyxPSV5/iss7a85sopvR9n09imvvp8XDfPNuccd10TQwGSKVt2vN7vSMrgQOsGEuBBsEmsdlRil4+x9NTYphsc/7vTV9pRN4U+ZI5yTbBof6SFcH5mwAatlZB/RztwA1wh3qUUgyLz/kTKqTRbaPc5W1+LDPBZnMiaTWq786418pio4oM86mVIthfE/34S0kn8GpJGzNF12NzbVQzbpv6YpmrPKG1FfaQPwc=\",\"q\":\"QNb5hnFnkT59E13r0xAxwLGNzASjG/n9EDuc8sIYWEcXPQsNpgPgKhlRsqbjxLACeHMkQ3ybaT/rspu6+92S7AxGIYk1n6OpW/RgObgFcHqqrMGvipfliekrz/FZZ215zZRTej7Pp7FNffT4uG+ebc447romhgMkUrbteb3ekZXAgdYMJcCDYJNY7KjFLx9j6amxTDY5/3emr7Sibwp8yRzkm2DQ/0kK4PzNgA1bKyD+jnbgBrhDvUopBkXn/ImVUmi20e5ytr8WGeCzOZE0mtV351xr5TFRxQZ51MqRbC+J/vwlpJP4NSSNmaLrsbm2qhm3Tf0xTNWeUNqK+0gfgw==\"},\"elements\":[\"cDEirCGORSF6n3eWy0zxrjASTNBes9xCXLEJL9AigcUtAEauqgzQEOE+r2SE1eIXHbf43Iv4RT1ffKD6msPbKOppmB6bdxOtlzHK/6HJxJ9z6zNa75OTB2NbRCxGLhuN92joFFN8uQqiZ87PBYE4ZmA347gjVSvJsolsoFki2r4gLRITFbo/0CYFZqUXUgIzbyTX7DYIjCWY9o4sXofd1Ay3QgdHvN6HEVpdUc62bMxu1q9wZopPFdA03fadGyKeQkT93EgIAcCSuMemfgNEcONEieMSg3pLIKy68dKfxIP7y09WdY4f+to6d+xJVU/61/Gp41yPNS9n9BruQgsdAA==\"]}}");

        ZpGroupElement egDerivedPublicKey = egPrivateKey.getGroup().getGenerator().exponentiate(egDerivedPrivateKey);

        VerificationSetEntity verificationSet = new VerificationSetEntity();
        VerificationSetData verificationSetData = new VerificationSetData();
        verificationSetData.setChoicesCodesEncryptionPublicKey(
            Base64.getEncoder().encodeToString(egPublicKey.toJson().getBytes(StandardCharsets.UTF_8)));
        verificationSet.setJSON(ObjectMappers.toJson(verificationSetData));

        Mockito.when(verificationSetRepositoryMock.findByTenantIdElectionEventIdVerificationCardSetId(tenantId,
            electionEventId, verificationCardSetId)).thenReturn(verificationSet);

        VerificationDerivedKeys verificationDerivedKeys = new VerificationDerivedKeys();
        verificationDerivedKeys.setBckDerivedExpCommitment(
            ObjectMappers.toJson(Arrays.asList(egDerivedPublicKey.toJson().getBytes(StandardCharsets.UTF_8))));

        Mockito.when(verificationDerivedKeysRepositoryMock.findByTenantIdElectionEventIdVerificationCardId(tenantId,
            electionEventId, verificationCardId)).thenReturn(verificationDerivedKeys);

        ChoiceCodesVerificationResPayload choiceCodesComputationResult = new ChoiceCodesVerificationResPayload();
        choiceCodesComputationResult.setCastCodeDerivedKeyJson(
            Base64.getEncoder().encodeToString(egDerivedPublicKey.toJson().getBytes(StandardCharsets.UTF_8)));
        choiceCodesComputationResult.setExponentiationProofJson(
            "{\"zkProof\":{\"hash\":\"AMA4hLeT7HlMCJgErVo4Cf6R/SEqD4t1ax6CLEuqHkYM\",\"values\":[\"DHmIYsWd2MYCw6rnzkmhc0EoJGTs0qW40mH4offKVATW71AMO9edGX3+rVc1zTKGoGqYhq8moGsts+H5wn/iD2jUG6mX1YyXVsG0RsgCcPkQVLCXpP+hRMcOVRWwBpMm2bUD3l2bDs3rkn23WrrHIKbtU39B29OSUwdKinJ5+oc1cWoyNLBDDST26TPvFCLFofhrI834nGYfoH0Q6AjuzOV7ioISB/0Qr4OEHmHhlHIwBUBTK9D0VxhN1LP4TxRIg7KgyZnpMhagoAwN254e43FQ9aXGa76ypZRZZHe9qiYKwU1ORDMht7TFDN+Ddv4exHihsNdMWdEZMdAXxVPJaw==\"],\"q\":\"QNb5hnFnkT59E13r0xAxwLGNzASjG/n9EDuc8sIYWEcXPQsNpgPgKhlRsqbjxLACeHMkQ3ybaT/rspu6+92S7AxGIYk1n6OpW/RgObgFcHqqrMGvipfliekrz/FZZ215zZRTej7Pp7FNffT4uG+ebc447romhgMkUrbteb3ekZXAgdYMJcCDYJNY7KjFLx9j6amxTDY5/3emr7Sibwp8yRzkm2DQ/0kK4PzNgA1bKyD+jnbgBrhDvUopBkXn/ImVUmi20e5ytr8WGeCzOZE0mtV351xr5TFRxQZ51MqRbC+J/vwlpJP4NSSNmaLrsbm2qhm3Tf0xTNWeUNqK+0gfgw==\"}}");
        Map<BigInteger, BigInteger> primeToComputedPrime = new LinkedHashMap<>();
        primeToComputedPrime.put(new BigInteger("1"), new BigInteger(
            "8022383711093351504105562720986627793732140839416875969421723695977748308877566136795295339105127265300615321223494808293397345435260529401045786587102529683389724392787112785021235620697515837942134918095406853031906291996994756975530570659570181288749044877579825880783249455007723332396934784537777378335647686648100360411352015339689641511178791124204817652205107964441202173123976789318765532752131540264073485304426982197195190219073632983523904619579754549940616963368912163027283647889749275783938951837862945602496460298883360369269158059254525891307757113856476488722323531965118355600858885264453839944817"));
        primeToComputedPrime.put(new BigInteger("2"), new BigInteger(
            "8769614426496905897871263371945162518060557886227114009709629513349830235439442585136238259254896354200796946429148047568296674268873356380436595141802446889029739584207797502535410833929489059813062668059468541432805004070249338190963543071666864424125955532681820673640617110435563871033872605807778568146986221182624614155218534370430560064226069957149558347115996305255869475893441862034471398652308876042451847337132674335225077672653956558513452562501839991210473231636802364500589679235701409754111345612340084177227148116559386665099887714783325865904522866239897373695177999873469947630044719484837032841583"));
        primeToComputedPrime.put(new BigInteger("3"), new BigInteger(
            "7753997773349114851014872595412229124935586069136109932557729361962700931250288598827914059384959169092463280400446234855834341027383856405694386611948793047898708826795761987868570894182383960024974939461746703520497431158943627515203889350173843690417967551372274912074718595291685616660268276362458051988601938414800016233074168742048729391680176586007830746091892906366178269044446346207273349125414975524690489068066478697927684932301394374952503818346679003000962896829504673431056921229890962405859115848306704984577545268476010519436663260485233878962015780652992432671991049046570952595444182669488949648837"));
        choiceCodesComputationResult.setPrimeToComputedPrime(primeToComputedPrime);
        
        // Execute
        ValidationResult validationResult =
            sut.validate(tenantId, electionEventId, verificationCardId, choiceCodesComputationResult);

        // Verify
        assertThat(validationResult.isResult(), is(true));
    }

    private static class ProofsServiceForTest implements ProofsServiceAPI {

        private ProofsServiceAPI proofService;

        public ProofsServiceForTest() {
            try {
                proofService = new ProofsService();
            } catch (GeneralCryptoLibException e) {
                System.err.println("Error initializing cryptoservice");
            }
        }

        @Override
        public ProofProverAPI createProofProverAPI(MathematicalGroup<?> group) throws GeneralCryptoLibException {
            return proofService.createProofProverAPI(group);
        }

        @Override
        public ProofVerifierAPI createProofVerifierAPI(MathematicalGroup<?> group) throws GeneralCryptoLibException {
            return proofService.createProofVerifierAPI(group);
        }

        @Override
        public ProofPreComputerAPI createProofPreComputerAPI(MathematicalGroup<?> group)
                throws GeneralCryptoLibException {
            return proofService.createProofPreComputerAPI(group);
        }

    }
    
    private static class PrimitivesServiceForTest implements PrimitivesServiceAPI {
        
        private PrimitivesServiceAPI primitivesService;
        
        public PrimitivesServiceForTest() {
            try {
                primitivesService = new PrimitivesService();
            } catch (GeneralCryptoLibException e) {
                System.err.println("Error initializing cryptoservice");
            }
        }

        @Override
        public CryptoAPIRandomBytes getCryptoRandomBytes() {  return null; }

        @Override
        public CryptoAPIRandomInteger getCryptoRandomInteger() { return null; }

        @Override
        public CryptoAPIRandomString get32CharAlphabetCryptoRandomString() { return null; }

        @Override
        public CryptoAPIRandomString get64CharAlphabetCryptoRandomString() { return null; }

        @Override
        public CryptoAPIKDFDeriver getKDFDeriver() { return null; }

        @Override
        public CryptoAPIPBKDFDeriver getPBKDFDeriver() { return null; }

        @Override
        public byte[] getHash(byte[] data) throws GeneralCryptoLibException { return primitivesService.getHash(data); }

        @Override
        public byte[] getHash(InputStream in) throws GeneralCryptoLibException { return null; }

        @Override
        public void shuffle(List<?> list) throws GeneralCryptoLibException { }

        @Override
        public FFCEngine getFFCEngine() { return null; }

        @Override
        public MessageDigest getRawMessageDigest() { return null; }
    }

}
