/*
 * 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.channel.cc.commons.test;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.Security;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import java.util.UUID;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.mathematical.groups.impl.ZpGroupElement;
import com.scytl.cryptolib.mathematical.groups.impl.ZpSubgroup;
import com.scytl.products.ov.channel.cc.choicecodes.verification.service.ChoiceCodesComputationService;
import com.scytl.products.ov.channel.cc.choicecodes.verification.service.ComputedVerificationCardExistsException;
import com.scytl.products.ov.channel.cc.choicecodes.verification.service.ComputedVerificationCardsRepository;
import com.scytl.products.ov.commons.beans.domain.model.confirmation.ConfirmationMessage;
import com.scytl.products.ov.commons.dto.BallotCastingKeyVerificationInput;
import com.scytl.products.ov.commons.dto.ChoiceCodeVerificationDTO;
import com.scytl.products.ov.commons.dto.ChoiceCodesVerificationResPayload;
import com.scytl.products.ov.commons.dto.PartialCodesInput;
import com.scytl.products.ov.commons.messaging.MessageListener;
import com.scytl.products.ov.commons.messaging.MessagingService;
import com.scytl.products.ov.commons.ui.ws.rs.ObjectMappers;

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {TestConfiguration.class })
@TestPropertySource(properties = {
        "queues.cg-comp.res=" + FakeVoteCastConfirmationTest.TEST_GENERATION_COMPUTATION_RESPONSE_QUEUE,
        "queues.cv-comp.res=" + FakeVoteCastConfirmationTest.TEST_VERIFICATION_COMPUTATION_RESPONSE_QUEUE })
public class FakeVoteCastConfirmationTest {

    static final String TEST_GENERATION_COMPUTATION_RESPONSE_QUEUE = "cg-comp-res";

    static final String TEST_VERIFICATION_COMPUTATION_RESPONSE_QUEUE = "cv-comp-res";

    private static String ELECTION_EVENT_ID = "electionEventId";

    private static String VERIFICATION_CARD_ID = "verificationCardId";

    @Autowired
    ComputedVerificationCardsRepository computedVerificationCardsRepository;

    @Autowired
    ChoiceCodesComputationService sut;

    @Autowired
    MessagingService messagingService;

    private static ZpSubgroup encryptionParameters;

    @BeforeClass
    public static void init() throws GeneralCryptoLibException {
        encryptionParameters = new ZpSubgroup(new BigInteger("2"),
            new BigInteger(
                "16370518994319586760319791526293535327576438646782139419846004180837103527129035954742043590609421369665944746587885814920851694546456891767644945459124422553763416586515339978014154452159687109161090635367600349264934924141746082060353483306855352192358732451955232000593777554431798981574529854314651092086488426390776811367125009551346089319315111509277347117467107914073639456805159094562593954195960531136052208019343392906816001017488051366518122404819967204601427304267380238263913892658950281593755894747339126531018026798982785331079065126375455293409065540731646939808640273393855256230820509217411510058759"),
            new BigInteger(
                "8185259497159793380159895763146767663788219323391069709923002090418551763564517977371021795304710684832972373293942907460425847273228445883822472729562211276881708293257669989007077226079843554580545317683800174632467462070873041030176741653427676096179366225977616000296888777215899490787264927157325546043244213195388405683562504775673044659657555754638673558733553957036819728402579547281296977097980265568026104009671696453408000508744025683259061202409983602300713652133690119131956946329475140796877947373669563265509013399491392665539532563187727646704532770365823469904320136696927628115410254608705755029379"));

        Security.addProvider(new BouncyCastleProvider());
    }

    // Simulation of Fake Vote Confirmation attack (SV-6259)
    @Test
    public void testFakeConfirmation()
            throws ComputedVerificationCardExistsException, GeneralCryptoLibException, JsonProcessingException {

        // Prepare computed state of VCid
        computedVerificationCardsRepository.saveComputedVerificationCard(ELECTION_EVENT_ID, VERIFICATION_CARD_ID);

        ZpGroupElement prime = new ZpGroupElement(new BigInteger("9887"), encryptionParameters);        
        List<BigInteger> normalPartialCodesElements = Arrays.asList(new BigInteger[]{ prime.getValue() });            

        // The malicious client multiplies BCK * G
        ZpGroupElement g = encryptionParameters.getGenerator();
        List<BigInteger> corruptedPartialCodesElements = Arrays.asList(new BigInteger[]{ prime.multiply(g).getValue() });
        
        // Execution of BCK computations
        ChoiceCodeVerificationDTO<ChoiceCodesVerificationResPayload> normalResults =
            compute(normalPartialCodesElements);
        
        ChoiceCodeVerificationDTO<ChoiceCodesVerificationResPayload> corruptedResults =
                compute(corruptedPartialCodesElements);

        // Test that the corrupted results can NOT be converted to normal results
        BigInteger corruptedComputedBck =
            corruptedResults.getPayload().getPrimeToComputedPrime().get(prime.multiply(g).getValue());
        ZpGroupElement egCorruptedComputedBck = new ZpGroupElement(corruptedComputedBck, encryptionParameters);
        
        // Malicious voting server tries to extract the computed BCK from the computed result
        ZpGroupElement derivedKey = ZpGroupElement
            .fromJson(new String(Base64.getDecoder().decode(corruptedResults.getPayload().getCastCodeDerivedKeyJson()),
                StandardCharsets.UTF_8));
         
        ZpGroupElement egExtractedComputedBck =
            egCorruptedComputedBck.multiply(derivedKey.invert());

        BigInteger normalComputedBck = normalResults.getPayload().getPrimeToComputedPrime().get(prime.getValue());
        ZpGroupElement egNormalComputedBck = new ZpGroupElement(normalComputedBck, encryptionParameters);

        Assert.assertNotEquals(egNormalComputedBck, egExtractedComputedBck);
    }

    private ChoiceCodeVerificationDTO<ChoiceCodesVerificationResPayload> compute(
            List<BigInteger> partialCodesElements) throws JsonProcessingException {
        ChoiceCodeVerificationDTO<PartialCodesInput> data = new ChoiceCodeVerificationDTO<>();
        data.setElectionEventId(ELECTION_EVENT_ID);
        data.setVerificationCardSetId("verificationCardSetId");
        data.setVerificationCardId(VERIFICATION_CARD_ID);
        data.setRequestId("requestId");
        data.setCorrelationId(UUID.randomUUID());

        data.setPayload(new PartialCodesInput());

        BallotCastingKeyVerificationInput bckVerificationInput = new BallotCastingKeyVerificationInput();
        ConfirmationMessage confirmationMessage = new ConfirmationMessage();
        String confirmationKey = Base64.getEncoder()
            .encodeToString(partialCodesElements.get(0).toString().getBytes(StandardCharsets.UTF_8));
        confirmationMessage.setConfirmationKey(confirmationKey);
        bckVerificationInput.setConfirmationMessage(ObjectMappers.toJson(confirmationMessage));
        bckVerificationInput.setVotingCardId("votingCardId");

        data.getPayload().setBallotCastingKeyVerificationInput(bckVerificationInput);
        data.getPayload().setPartialCodesElements(partialCodesElements);

        ((MessageListener) sut).onMessage(data);

        @SuppressWarnings("unchecked")
        ChoiceCodeVerificationDTO<ChoiceCodesVerificationResPayload> verificationComputationResult =
            (ChoiceCodeVerificationDTO<ChoiceCodesVerificationResPayload>) ((MessagingServiceMockImpl) messagingService)
                .getQueueMock().get(TEST_VERIFICATION_COMPUTATION_RESPONSE_QUEUE);

        return verificationComputationResult;
    }

}
