/*
 * 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
 */
/* jshint node:true */
'use strict';

var SimplePlaintextEqualityProofTestData =
    require('./data/simple-plaintext-equality-proof-data');
var cryptoPolicy = require('scytl-cryptopolicy');
var zkProof = require('../lib/index');
var elGamal = require('scytl-elgamal');
var messageDigest = require('scytl-messagedigest');
var mathematical = require('scytl-mathematical');
var secureRandom = require('scytl-securerandom');
var codec = require('scytl-codec');

describe('The zero-knowledge proof module should be able to ...', function() {
  var proofService_;
  var group_;
  var secret_;
  var anotherSecret_;
  var primaryPublicKey_;
  var secondaryPublicKey_;
  var anotherPrimaryPublicKey_;
  var anotherSecondaryPublicKey_;
  var primaryCiphertext_;
  var secondaryCiphertext_;
  var anotherPrimaryCiphertext_;
  var anotherSecondaryCiphertext_;
  var data_;
  var otherData_;
  var secretForNonSimplePlaintext_;
  var primaryCiphertextForNonSimplePlaintext_;
  var secondaryCiphertextForNonSimplePlaintext_;
  var proofHandler_;
  var proof_;
  var preComputation_;

  beforeAll(function() {
    proofService_ = zkProof.newService();

    var testData = new SimplePlaintextEqualityProofTestData();
    var elGamalService = elGamal.newService();
    group_ = testData.getGroup();
    var primaryEncryptedElements = testData.getPrimaryEncryptedElements();
    secret_ = primaryEncryptedElements.secret;
    var otherPrimaryEncryptedElements =
        testData.getOtherPrimaryEncryptedElements();
    anotherSecret_ = otherPrimaryEncryptedElements.secret;
    primaryPublicKey_ = testData.getPrimaryPublicKey();
    secondaryPublicKey_ = testData.getSecondaryPublicKey();
    anotherPrimaryPublicKey_ = testData.getAnotherPrimaryPublicKey();
    anotherSecondaryPublicKey_ = testData.getAnotherSecondaryPublicKey();
    primaryCiphertext_ = elGamalService.newEncryptedElements(
        primaryEncryptedElements.gamma, primaryEncryptedElements.phis);
    var secondaryEncryptedElements = testData.getSecondaryEncryptedElements();
    secondaryCiphertext_ = elGamalService.newEncryptedElements(
        secondaryEncryptedElements.gamma, secondaryEncryptedElements.phis);
    anotherPrimaryCiphertext_ = testData.getOtherPrimaryEncryptedElements();
    anotherSecondaryCiphertext_ = testData.getOtherSecondaryEncryptedElements();
    data_ = testData.getStringData();
    otherData_ = testData.getOtherStringData();
    var primaryEncryptedElementsForNonSimplePlaintext =
        testData.getPrimaryEncryptedElementsForNonSimplePlaintext();
    secretForNonSimplePlaintext_ =
        primaryEncryptedElementsForNonSimplePlaintext.secret;
    primaryCiphertextForNonSimplePlaintext_ =
        elGamalService.newEncryptedElements(
            primaryEncryptedElementsForNonSimplePlaintext.gamma,
            primaryEncryptedElementsForNonSimplePlaintext.phis);
    var secondaryEncryptedElementsForNonSimplePlaintext =
        testData.getSecondaryEncryptedElementsForNonSimplePlaintext();
    secondaryCiphertextForNonSimplePlaintext_ =
        elGamalService.newEncryptedElements(
            secondaryEncryptedElementsForNonSimplePlaintext.gamma,
            secondaryEncryptedElementsForNonSimplePlaintext.phis);

    proofHandler_ =
        proofService_.newSimplePlaintextEqualityProofHandler(group_).init(
            primaryPublicKey_, secondaryPublicKey_);
    proof_ = proofHandler_.generate(
        secret_, primaryCiphertext_, secondaryCiphertext_);
    preComputation_ = proofHandler_.preCompute();
  });

  beforeEach(function() {
    proofHandler_ =
        proofService_.newSimplePlaintextEqualityProofHandler(group_).init(
            primaryPublicKey_, secondaryPublicKey_);
  });

  describe('create a zero-knowledge proof service that should be able to ..', function() {
    describe('create a simple plaintext equality proof handler that should be able to', function() {
      it('generate and verify a simple plaintext equality proof', function() {
        var verified = proofHandler_.verify(
            proof_, primaryCiphertext_, secondaryCiphertext_);
        expect(verified).toBeTruthy();
      });

      it('generate and verify a simple plaintext equality proof, using a specified cryptographic policy',
         function() {
           var policy = cryptoPolicy.newInstance();
           policy.proofs.messageDigest.algorithm =
               cryptoPolicy.options.proofs.messageDigest.algorithm.SHA512_224;

           var proofService = zkProof.newService({policy: policy});
           var proofHandler =
               proofService.newSimplePlaintextEqualityProofHandler(group_).init(
                   primaryPublicKey_, secondaryPublicKey_);

           proof_ = proofHandler.generate(
               secret_, primaryCiphertext_, secondaryCiphertext_);

           var verified = proofHandler.verify(
               proof_, primaryCiphertext_, secondaryCiphertext_);
           expect(verified).toBeTruthy();
         });

      it('generate and verify a simple plaintext equality proof, using a specified message digest service',
         function() {
           var policy = cryptoPolicy.newInstance();
           policy.proofs.messageDigest.algorithm =
               cryptoPolicy.options.proofs.messageDigest.algorithm.SHA512_224;
           var messageDigestService =
               messageDigest.newService({policy: policy});

           var proofService =
               zkProof.newService({messageDigestService: messageDigestService});
           var proofHandler =
               proofService.newSimplePlaintextEqualityProofHandler(group_).init(
                   primaryPublicKey_, secondaryPublicKey_);

           proof_ = proofHandler.generate(
               secret_, primaryCiphertext_, secondaryCiphertext_);

           var verified = proofHandler.verify(
               proof_, primaryCiphertext_, secondaryCiphertext_);
           expect(verified).toBeTruthy();
         });

      it('generate and verify a simple plaintext equality proof, using a specified secure random service object',
         function() {
           var proofService = zkProof.newService(
               {secureRandomService: secureRandom.newService()});
           var proofHandler =
               proofService.newSimplePlaintextEqualityProofHandler(group_).init(
                   primaryPublicKey_, secondaryPublicKey_);

           proof_ = proofHandler.generate(
               secret_, primaryCiphertext_, secondaryCiphertext_);

           var verified = proofHandler.verify(
               proof_, primaryCiphertext_, secondaryCiphertext_);
           expect(verified).toBeTruthy();
         });

      it('generate and verify a simple plaintext equality proof, using a specified mathematical service object',
         function() {
           var proofService = zkProof.newService(
               {mathematicalService: mathematical.newService()});
           var proofHandler =
               proofService.newSimplePlaintextEqualityProofHandler(group_).init(
                   primaryPublicKey_, secondaryPublicKey_);

           proof_ = proofHandler.generate(
               secret_, primaryCiphertext_, secondaryCiphertext_);

           var verified = proofHandler.verify(
               proof_, primaryCiphertext_, secondaryCiphertext_);
           expect(verified).toBeTruthy();
         });

      it('generate and verify a plaintext proof, using provided auxiliary data',
         function() {
           // Data as string.
           var proof = proofHandler_.generate(
               secret_, primaryCiphertext_, secondaryCiphertext_,
               {data: data_});
           var verified = proofHandler_.verify(
               proof, primaryCiphertext_, secondaryCiphertext_, {data: data_});
           expect(verified).toBeTruthy();

           // Data as bytes.
           proof = proofHandler_.generate(
               secret_, primaryCiphertext_, secondaryCiphertext_,
               {data: codec.utf8Encode(data_)});
           verified = proofHandler_.verify(
               proof, primaryCiphertext_, secondaryCiphertext_,
               {data: codec.utf8Encode(data_)});
           expect(verified).toBeTruthy();
         });

      it('generate and verify a simple plaintext equality proof, using a pre-computation',
         function() {
           var proof = proofHandler_.generate(
               secret_, primaryCiphertext_, secondaryCiphertext_,
               {preComputation: preComputation_});

           var verified = proofHandler_.verify(
               proof, primaryCiphertext_, secondaryCiphertext_);
           expect(verified).toBeTruthy();
         });

      it('generate and verify a simple plaintext equality proof, using empty auxiliary data',
         function() {
           var proof = proofHandler_.generate(
               secret_, primaryCiphertext_, secondaryCiphertext_, {data: ''});

           var verified = proofHandler_.verify(
               proof, primaryCiphertext_, secondaryCiphertext_, {data: ''});
           expect(verified).toBeTruthy();
         });

      it('pre-compute, generate and verify a simple plaintext proof, using method chaining',
         function() {
           var preComputation =
               proofService_.newSimplePlaintextEqualityProofHandler(group_)
                   .init(primaryPublicKey_, secondaryPublicKey_)
                   .preCompute();

           var proof =
               proofService_.newSimplePlaintextEqualityProofHandler(group_)
                   .init(primaryPublicKey_, secondaryPublicKey_)
                   .generate(
                       secret_, primaryCiphertext_, secondaryCiphertext_,
                       {data: data_, preComputation: preComputation});

           var verified =
               proofService_.newSimplePlaintextEqualityProofHandler(group_)
                   .init(primaryPublicKey_, secondaryPublicKey_)
                   .verify(
                       proof, primaryCiphertext_, secondaryCiphertext_,
                       {data: data_});
           expect(verified).toBeTruthy();
         });

      it('create a new simple plaintext equality ZeroKnowledgeProof object',
         function() {
           var hash = proof_.hash;
           var values = proof_.values;
           var proof = proofService_.newProof(hash, values);

           var verified = proofHandler_.verify(
               proof, primaryCiphertext_, secondaryCiphertext_);
           expect(verified).toBeTruthy();
         });

      it('create a new simple plaintext equality ZeroKnowledgeProofPreComputation object',
         function() {
           var exponents = preComputation_.exponents;
           var phiOutputs = preComputation_.phiOutputs;
           var preComputation =
               proofService_.newPreComputation(exponents, phiOutputs);

           var proof = proofHandler_.generate(
               secret_, primaryCiphertext_, secondaryCiphertext_,
               {preComputation: preComputation});

           var verified = proofHandler_.verify(
               proof, primaryCiphertext_, secondaryCiphertext_);
           expect(verified).toBeTruthy();
         });

      it('serialize and deserialize a simple plaintext equality', function() {
        var proofJson = proof_.toJson();

        var proof = proofService_.newProof(proofJson);

        var verified = proofHandler_.verify(
            proof, primaryCiphertext_, secondaryCiphertext_);
        expect(verified).toBeTruthy();
      });

      it('serialize and deserialize a simple plaintext equality pre-computation',
         function() {
           var preComputationJson = preComputation_.toJson();

           var preComputation =
               proofService_.newPreComputation(preComputationJson);

           var proof = proofHandler_.generate(
               secret_, primaryCiphertext_, secondaryCiphertext_,
               {preComputation: preComputation});

           var verified = proofHandler_.verify(
               proof, primaryCiphertext_, secondaryCiphertext_);
           expect(verified).toBeTruthy();
         });

      it('fail to verify a simple plaintext equality proof that was generated with a secret not used to generate the ciphertexts',
         function() {
           var proof = proofHandler_.generate(
               anotherSecret_, primaryCiphertext_, secondaryCiphertext_);

           var verified = proofHandler_.verify(
               proof, primaryCiphertext_, secondaryCiphertext_);
           expect(verified).toBeFalsy();
         });

      it('fail to verify a simple plaintext equality proof that was generated with a primary publicKey not used to generate the primary ciphertext',
         function() {
           proofHandler_.init(anotherPrimaryPublicKey_, secondaryPublicKey_);

           var proof = proofHandler_.generate(
               secret_, primaryCiphertext_, secondaryCiphertext_);

           var verified = proofHandler_.verify(
               proof, primaryCiphertext_, secondaryCiphertext_);
           expect(verified).toBeFalsy();
         });

      it('fail to verify a simple plaintext equality proof that was generated with a secondary publicKey not used to generate the secondary ciphertext',
         function() {
           proofHandler_.init(primaryPublicKey_, anotherSecondaryPublicKey_);

           var proof = proofHandler_.generate(
               secret_, primaryCiphertext_, secondaryCiphertext_);

           var verified = proofHandler_.verify(
               proof, primaryCiphertext_, secondaryCiphertext_);
           expect(verified).toBeFalsy();
         });

      it('fail to verify a simple plaintext equality proof that was generated with a primary ciphertext not generated from the simple plaintext',
         function() {
           var proof = proofHandler_.generate(
               secret_, anotherPrimaryCiphertext_, secondaryCiphertext_);

           var verified = proofHandler_.verify(
               proof, anotherPrimaryCiphertext_, secondaryCiphertext_);
           expect(verified).toBeFalsy();
         });

      it('fail to verify a simple plaintext equality proof that was generated with a secondary ciphertext not generated from the simple plaintext',
         function() {
           var proof = proofHandler_.generate(
               secret_, primaryCiphertext_, anotherSecondaryCiphertext_);

           var verified = proofHandler_.verify(
               proof, primaryCiphertext_, anotherSecondaryCiphertext_);
           expect(verified).toBeFalsy();
         });

      it('fail to verify a simple plaintext equality proof that was generated with plaintext that is not simple',
         function() {
           var proof = proofHandler_.generate(
               secretForNonSimplePlaintext_,
               primaryCiphertextForNonSimplePlaintext_,
               secondaryCiphertextForNonSimplePlaintext_);

           var verified = proofHandler_.verify(
               proof, primaryCiphertextForNonSimplePlaintext_,
               secondaryCiphertextForNonSimplePlaintext_);
           expect(verified).toBeFalsy();
         });

      it('fail to verify a simple plaintext equality proof when using a primary public key not used to generate the proof',
         function() {
           var verified =
               proofHandler_.init(anotherPrimaryPublicKey_, secondaryPublicKey_)
                   .verify(proof_, primaryCiphertext_, secondaryCiphertext_);
           expect(verified).toBeFalsy();
         });

      it('fail to verify a simple plaintext equality proof when using a secondary public key not used to generate the proof',
         function() {
           var verified =
               proofHandler_.init(primaryPublicKey_, anotherSecondaryPublicKey_)
                   .verify(proof_, primaryCiphertext_, secondaryCiphertext_);
           expect(verified).toBeFalsy();
         });

      it('fail to verify a simple plaintext equality proof when using a primary ciphertext not used to generate the proof',
         function() {
           var verified = proofHandler_.verify(
               proof_, anotherPrimaryCiphertext_, secondaryCiphertext_);
           expect(verified).toBeFalsy();
         });

      it('fail to verify a simple plaintext equality proof when using a secondary ciphertext not used to generate the proof',
         function() {
           var verified = proofHandler_.verify(
               proof_, primaryCiphertext_, anotherSecondaryCiphertext_);
           expect(verified).toBeFalsy();
         });

      it('fail to verify a simple plaintext equality proof when using auxiliary data not used to generate the proof',
         function() {
           var proof = proofHandler_.generate(
               secret_, primaryCiphertext_, secondaryCiphertext_,
               {data: data_});

           var verified = proofHandler_.verify(
               proof, primaryCiphertext_, secondaryCiphertext_,
               {data: otherData_});
           expect(verified).toBeFalsy();
         });

      it('throw an error when generating a proof before the handler has been initialized with any pubilc key',
         function() {
           var proofHandler =
               proofService_.newSimplePlaintextEqualityProofHandler(group_);

           expect(function() {
             proofHandler.generate(
                 secret_, primaryCiphertext_, secondaryCiphertext_);
           }).toThrow();
         });

      it('throw an error when pre-computing a proof before the handler has been initialized with any pubilc key',
         function() {
           var proofHandler =
               proofService_.newSimplePlaintextEqualityProofHandler(group_);

           expect(function() {
             proofHandler.preCompute();
           }).toThrow();
         });

      it('throw an error when verifying a proof before the handler has been initialized with any pubilc key',
         function() {
           var proofHandler =
               proofService_.newSimplePlaintextEqualityProofHandler(group_);

           expect(function() {
             proofHandler.verify(
                 proof_, primaryCiphertext_, secondaryCiphertext_);
           }).toThrow();
         });
    });
  });
});
