/*
 * 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
 */
describe('Mathematical ...', function() {
  'use strict';

  var G_STR = '2';
  var P_STR = '23';
  var Q_STR = '11';
  var GROUP_ELEMENT_STR = '3';
  var EXPONENT_STR = '2';
  var EXPONENT_1_LARGE_STR =
      '129547331161844130899924173735104728702220700682642138851022406761777994632728337153635583150724889632689084497665212926336088840842855597736181764744122977345480665146165524664735861913761423291109155186483836806324970977794125768575887671259015293979900588616487195411477129839925141145884519189238456836206862720534398725375349481201999965487100563522135988094619173050092410477941592825803862292425503577691441721033806466916501473758812704373810862790693842410609705251142958265348495650713103821889837809522418329172422392659115758749365074645804533890934339070499112165048929164809093509978635413193830265570';
  var EXPONENT_2_LARGE_STR =
      '7533608948657130117198364792462971241599837777629317286887202126604432685322243335715285630236984093165224740099563982076007648728594011814311120331313200895892632245003875959905589367483155794698953057915454892457379023058909518440394905695255170407948701433659375513788930419544151891518689331636455316691827048845766063478445484089208223819510250476623436782404385896353292653750744111841510466992101967325081746747885221256692886521245630131046183525331085171958919416648097910028887247120874015121272894655357197879292253781160838729753609784455892951965051119314203469298666741439497193677501351952497150273389';
  var GROUP_ELEMENT_1_STR = '2';
  var GROUP_ELEMENT_2_STR = '3';
  var GROUP_ELEMENT_3_STR = '9';
  var GROUP_ELEMENT_VALUES_FOR_LARGE_ARRAY = [3, 4, 6, 8, 9, 12, 13, 16, 18];

  var _initialized = false;
  var _g;
  var _p;
  var _q;
  var _groupElementValue;
  var _exponentValue;
  var _zpSubgroup;
  var _zpSubgroupJson;
  var _zpSubgroupElem;
  var _zpSubgroupElemJson;
  var _exponent;
  var _exponentJson;
  var _exponent1Large;
  var _exponent2Large;
  var _groupElement1Value;
  var _groupElement2Value;
  var _groupElement3Value;
  var _zpGroupElementArray;
  var _zpGroupElementArrayLarge;

  beforeEach(function() {
    if (!_initialized) {
      cryptolib('commons.utils', 'commons.mathematical', function(box) {
        var converters = new box.commons.utils.Converters();

        _g = new forge.jsbn.BigInteger(G_STR);
        _p = new forge.jsbn.BigInteger(P_STR);
        _q = new forge.jsbn.BigInteger(Q_STR);
        _groupElementValue = new forge.jsbn.BigInteger(GROUP_ELEMENT_STR);
        _exponentValue = new forge.jsbn.BigInteger(EXPONENT_STR);

        var gB64 = converters.base64FromBigInteger(_g);
        var pB64 = converters.base64FromBigInteger(_p);
        var qB64 = converters.base64FromBigInteger(_q);
        var expValueB64 = converters.base64FromBigInteger(_exponentValue);
        var elemValueB64 = converters.base64FromBigInteger(_groupElementValue);

        _zpSubgroup = new box.commons.mathematical.ZpSubgroup(_g, _p, _q);
        _zpSubgroupJson = '{"zpSubgroup":{"p":"' + pB64 + '","q":"' + qB64 +
            '","g":"' + gB64 + '"}}';

        _zpSubgroupElem = new box.commons.mathematical.ZpGroupElement(
            _groupElementValue, _p, _q);
        _zpSubgroupElemJson = '{"zpGroupElement":{"p":"' + pB64 + '","q":"' +
            qB64 + '","value":"' + elemValueB64 + '"}}';

        _exponent = new box.commons.mathematical.Exponent(_q, _exponentValue);
        _exponentJson =
            '{"exponent":{"q":"' + qB64 + '","value":"' + expValueB64 + '"}}';

        var exponentValue = new box.forge.jsbn.BigInteger(EXPONENT_1_LARGE_STR);
        _exponent1Large =
            new box.commons.mathematical.Exponent(_q, exponentValue);
        exponentValue = new box.forge.jsbn.BigInteger(EXPONENT_2_LARGE_STR);
        _exponent2Large =
            new box.commons.mathematical.Exponent(_q, exponentValue);

        var groupElement;
        _zpGroupElementArray = [];
        _groupElement1Value =
            new box.forge.jsbn.BigInteger(GROUP_ELEMENT_1_STR);
        groupElement = new box.commons.mathematical.ZpGroupElement(
            _groupElement1Value, _p, _q);
        _zpGroupElementArray.push(groupElement);
        _groupElement2Value =
            new box.forge.jsbn.BigInteger(GROUP_ELEMENT_2_STR);
        groupElement = new box.commons.mathematical.ZpGroupElement(
            _groupElement2Value, _p, _q);
        _zpGroupElementArray.push(groupElement);
        _groupElement3Value =
            new box.forge.jsbn.BigInteger(GROUP_ELEMENT_3_STR);
        groupElement = new box.commons.mathematical.ZpGroupElement(
            _groupElement3Value, _p, _q);
        _zpGroupElementArray.push(groupElement);

        _zpGroupElementArrayLarge = [];
        for (var i = 0; i < GROUP_ELEMENT_VALUES_FOR_LARGE_ARRAY.length; i++) {
          var value = new box.forge.jsbn.BigInteger(
              '' + GROUP_ELEMENT_VALUES_FOR_LARGE_ARRAY[i]);
          groupElement =
              new box.commons.mathematical.ZpGroupElement(value, _p, _q);
          _zpGroupElementArrayLarge.push(groupElement);
        }
      });

      _initialized = true;
    }
  });

  it('should exist as a module', function() {
    cryptolib(function(box) {
      expect(box.commons.mathematical).toBeDefined();
    });
  });

  describe('ZpSubgroup ..', function() {
    it('should be defined', function() {
      expect(_zpSubgroup).toBeDefined();
      expect(_zpSubgroup.getIdentity()).toBeDefined();
    });

    it('should be properly serialized', function() {
      var zpSubgroupJson = _zpSubgroup.stringify();
      expect(zpSubgroupJson).toBe(_zpSubgroupJson);
    });

    it('should be able to check group membership', function() {
      cryptolib('commons.mathematical', function(box) {
        var zpGroupElement = new box.commons.mathematical.ZpGroupElement(
            box.forge.jsbn.BigInteger.ONE, _p, _q);
        expect(_zpSubgroup.isGroupMember(zpGroupElement)).toBeTruthy();

        // check membership of member with different q
        var q2 = new box.forge.jsbn.BigInteger('19');
        var p2 = new box.forge.jsbn.BigInteger('37');
        var zpSubgroup2 = new box.commons.mathematical.ZpSubgroup(_g, p2, q2);
        var zpGroupElement2 = new box.commons.mathematical.ZpGroupElement(
            box.forge.jsbn.BigInteger.ONE, zpSubgroup2.getP(),
            zpSubgroup2.getQ());
        expect(!_zpSubgroup.isGroupMember(zpGroupElement2)).toBeTruthy();

        // check membership of member with positive value but not within the set
        zpGroupElement = new box.commons.mathematical.ZpGroupElement(
            new box.forge.jsbn.BigInteger('5'), _p, _q);
        expect(!_zpSubgroup.isGroupMember(zpGroupElement)).toBeTruthy();

        // throw error when the given element is incorrect
        var wrongElement;
        expect(!_zpSubgroup.isGroupMember(wrongElement)).toBeTruthy();
      });
    });

    it('should be able to compare with other groups', function() {
      cryptolib('commons.mathematical', function(box) {
        var zpSubgroup2 = new box.commons.mathematical.ZpSubgroup(_g, _p, _q);

        var q2 = new box.forge.jsbn.BigInteger('19');
        var p2 = new box.forge.jsbn.BigInteger('37');
        var zpSubgroup3 = new box.commons.mathematical.ZpSubgroup(_g, p2, q2);

        expect(_zpSubgroup.equals(zpSubgroup2)).toBeTruthy();
        expect(!_zpSubgroup.equals(zpSubgroup3)).toBeTruthy();
      });
    });
  });

  describe('ZpGroupElement ..', function() {
    it('should be defined', function() {
      expect(_zpSubgroupElem).toBeDefined();
      expect(_zpSubgroupElem.getElementValue().equals(_groupElementValue))
          .toBeTruthy();
    });

    it('should be properly serialized', function() {
      var zpGroupElementJson = _zpSubgroupElem.stringify();
      expect(zpGroupElementJson).toBe(_zpSubgroupElemJson);
    });

    it('should be able to multiply', function() {
      cryptolib('commons.mathematical', function(box) {
        var value = new box.forge.jsbn.BigInteger('2');
        var zpGroupElement2 =
            new box.commons.mathematical.ZpGroupElement(value, _p, _q);
        var zpSubgroupResult = _zpSubgroupElem.multiply(zpGroupElement2);

        expect(zpSubgroupResult).toBeDefined();
        expect(zpSubgroupResult.getElementValue().equals(
                   new box.forge.jsbn.BigInteger('6')))
            .toBeTruthy();
      });
    });

    it('should be able to exponentiate', function() {
      cryptolib('commons.mathematical', function(box) {
        var value = new box.forge.jsbn.BigInteger('2');
        var zpGroupElement2 = new box.commons.mathematical.Exponent(_q, value);
        var zpSubgroupResult = _zpSubgroupElem.exponentiate(zpGroupElement2);

        expect(zpSubgroupResult).toBeDefined();
        expect(zpSubgroupResult.getElementValue().equals(
                   new box.forge.jsbn.BigInteger('9')))
            .toBeTruthy();
      });
    });

    it('should be able to invert', function() {
      cryptolib(function(box) {
        var zpSubgroupResult = _zpSubgroupElem.invert();

        expect(zpSubgroupResult).toBeDefined();
        expect(zpSubgroupResult.getElementValue().equals(
                   new box.forge.jsbn.BigInteger('8')))
            .toBeTruthy();
      });
    });
  });

  describe('Exponent ..', function() {
    it('should be defined', function() {
      expect(_exponent).toBeDefined();
      expect(_exponent.getValue().equals(_exponentValue)).toBeTruthy();
    });

    it('should be properly serialized', function() {
      var exponentJson = _exponent.stringify();
      expect(exponentJson).toBe(_exponentJson);
    });

    it('should be able to add another exponent', function() {
      cryptolib('commons.mathematical', function(box) {
        var value = new box.forge.jsbn.BigInteger('3');
        var exponent2 = new box.commons.mathematical.Exponent(_q, value);

        var exponentResult = _exponent.add(exponent2);
        expect(exponentResult).toBeDefined();
        expect(exponentResult.getValue().equals(
                   new box.forge.jsbn.BigInteger('5')))
            .toBeTruthy();
      });
    });

    it('should be able to subtract another exponent', function() {
      cryptolib('commons.mathematical', function(box) {
        var value = new box.forge.jsbn.BigInteger('5');
        var exponent2 = new box.commons.mathematical.Exponent(_q, value);

        var exponentResult = exponent2.subtract(_exponent);
        expect(exponentResult).toBeDefined();
        expect(exponentResult.getValue().equals(
                   new box.forge.jsbn.BigInteger('3')))
            .toBeTruthy();
      });
    });

    it('should be able to multiply another exponent', function() {
      cryptolib('commons.mathematical', function(box) {
        var value = new box.forge.jsbn.BigInteger('3');
        var exponent2 = new box.commons.mathematical.Exponent(_q, value);

        var exponentResult = _exponent.multiply(exponent2);
        expect(exponentResult).toBeDefined();
        expect(exponentResult.getValue().toString())
            .toEqual('' + _exponent.getValue() * exponent2.getValue() % _q);
      });
    });

    it('should be able to multiply exponents with large values', function() {
      var exponentResult = _exponent1Large.multiply(_exponent2Large);
      expect(exponentResult).toBeDefined();
    });

    it('should be able to negate an exponent', function() {
      cryptolib(function(box) {
        var exponentResult = _exponent.negate();

        expect(exponentResult).toBeDefined();
        expect(exponentResult.getValue().equals(
                   new box.forge.jsbn.BigInteger('9')))
            .toBeTruthy();
      });
    });

    it('should throw error when multiplying an exponent with a different q parameter',
       function() {
         cryptolib('commons.mathematical', function(box) {
           var exponent2 = new box.commons.mathematical.Exponent(
               new forge.jsbn.BigInteger('89'), _exponentValue);

           expect(function() {
             _exponent.multiply(exponent2);
           }).toThrow();
         });
       });

    it('should throw error when multiplying an exponent which is undefined',
       function() {
         expect(function() {
           _exponent.multiply(undefined);
         }).toThrow();
       });

    it('should throw error when multiplying an exponent which is null',
       function() {
         expect(function() {
           _exponent.multiply(null);
         }).toThrow();
       });
  });

  describe('groupUtils ..', function() {
    it('should be able to deserialize a ZpSubgroup', function() {
      cryptolib('commons.mathematical', function(box) {
        var zpSubgroupFromJson =
            box.commons.mathematical.groupUtils.deserializeGroup(
                _zpSubgroupJson);

        expect(zpSubgroupFromJson).toBeDefined();
        expect(zpSubgroupFromJson.getP()).toEqual(_p);
        expect(zpSubgroupFromJson.getQ()).toEqual(_q);
        expect(zpSubgroupFromJson.getGenerator().getElementValue()).toEqual(_g);
      });
    });

    it('should be able to deserialize a ZpGroupElement', function() {
      cryptolib('commons.mathematical', function(box) {
        var zpGroupElementFromJson =
            box.commons.mathematical.groupUtils.deserializeGroupElement(
                _zpSubgroupElemJson);

        expect(zpGroupElementFromJson).toBeDefined();
        expect(zpGroupElementFromJson.equals(_zpSubgroupElem)).toBeTruthy();
      });
    });

    it('should be able to deserialize an Exponent', function() {
      cryptolib('commons.mathematical', function(box) {
        var exponentFromJson =
            box.commons.mathematical.groupUtils.deserializeExponent(
                _exponentJson);

        expect(exponentFromJson).toBeDefined();
        expect(exponentFromJson.equals(_exponent)).toBeTruthy();
      });
    });

    it('should be able to compress group elements', function() {
      cryptolib('commons.mathematical', function(box) {
        var compressedElement =
            box.commons.mathematical.groupUtils.compressGroupElements(
                _zpSubgroup, _zpGroupElementArray);
        expect(compressedElement).toBeDefined();
        expect(compressedElement.getElementValue().equals(
                   new forge.jsbn.BigInteger('8')))
            .toBeTruthy();

        compressedElement =
            box.commons.mathematical.groupUtils.compressGroupElements(
                _zpSubgroup, _zpGroupElementArrayLarge);
        expect(compressedElement).toBeDefined();
        expect(compressedElement.getElementValue().equals(
                   new forge.jsbn.BigInteger('12')))
            .toBeTruthy();

        expect(function() {
          var zpSubgroupNOTDEFINED;
          box.commons.mathematical.groupUtils.compressGroupElements(
              zpSubgroupNOTDEFINED, _zpGroupElementArrayLarge);
        }).toThrow();
      });
    });

    it('should be able to build an array by compressing the final element',
       function() {
         cryptolib('commons.mathematical', function(box) {
           var requiredLength = 4;

           var compressedArray =
               box.commons.mathematical.groupUtils
                   .buildListWithCompressedFinalElement(
                       _zpSubgroup, _zpGroupElementArrayLarge, requiredLength);
           expect(compressedArray.length).toBe(requiredLength);

           var expectedCompressedArray = [];
           var expectedCompressedArrayValues = [3, 4, 6, 4];
           for (var i = 0; i < expectedCompressedArrayValues.length; i++) {
             var value = new box.forge.jsbn.BigInteger(
                 '' + expectedCompressedArrayValues[i]);
             var groupElement =
                 new box.commons.mathematical.ZpGroupElement(value, _p, _q);
             expectedCompressedArray.push(groupElement);
           }

           var equals = true;
           for (var j = 0; j < compressedArray.length; j++) {
             if (!compressedArray[j].equals(expectedCompressedArray[j])) {
               equals = false;
             }
           }
           expect(equals).toBeTruthy();

           expect(function() {
             var zpSubgroupNOTDEFINED;
             box.commons.mathematical.groupUtils
                 .buildListWithCompressedFinalElement(
                     zpSubgroupNOTDEFINED, _zpGroupElementArrayLarge,
                     requiredLength);
           }).toThrow();
         });
       });

    it('should be able to build an array by compressing the final exponent',
       function() {
         cryptolib('commons.mathematical', function(box) {
           var requiredLength = 4;

           var value;
           var exponent;

           var exponentValues = [1, 2, 3, 4, 6, 8, 9, 12, 13, 16, 18];
           var exponentArray = [];
           for (var i = 0; i < exponentValues.length; i++) {
             value = new box.forge.jsbn.BigInteger('' + exponentValues[i]);
             exponent = new box.commons.mathematical.Exponent(_q, value);
             exponentArray.push(exponent);
           }

           var compressedArray =
               box.commons.mathematical.groupUtils
                   .buildListWithCompressedFinalExponent(
                       _zpSubgroup, exponentArray, requiredLength);
           expect(compressedArray.length).toBe(requiredLength);

           var expectedCompressedArray = [];
           var expectedCompressedArrayValues = [1, 2, 3, 9];
           for (var j = 0; j < expectedCompressedArrayValues.length; j++) {
             value = new box.forge.jsbn.BigInteger(
                 '' + expectedCompressedArrayValues[j]);
             exponent = new box.commons.mathematical.Exponent(_q, value);
             expectedCompressedArray.push(exponent);
           }

           var equals = true;
           for (var k = 0; k < compressedArray.length; k++) {
             if (!compressedArray[k].equals(expectedCompressedArray[k])) {
               equals = false;
             }
           }
           expect(equals).toBeTruthy();
         });
       });

    it('should be able to generate a random Exponent', function() {
      cryptolib(
          'commons.mathematical', 'primitives.securerandom', function(box) {
            var zpSubgroup =
                box.commons.mathematical.groupUtils.buildZpSubgroupFromPAndG(
                    _p, _g);

            var secureRandomFactory =
                new box.primitives.securerandom.factory.SecureRandomFactory();
            var cryptoRandomInteger =
                secureRandomFactory.getCryptoRandomInteger();

            var randomExponent =
                box.commons.mathematical.groupUtils.generateRandomExponent(
                    zpSubgroup, cryptoRandomInteger);
            expect(randomExponent).toBeDefined();

            expect(function() {
              var zpSubgroupNOTDEFINED;
              box.commons.mathematical.groupUtils.generateRandomExponent(
                  zpSubgroupNOTDEFINED, cryptoRandomInteger);
            }).toThrow();

            expect(function() {
              box.commons.mathematical.groupUtils.generateRandomExponent(
                  1, cryptoRandomInteger);
            }).toThrow();
          });
    });

    it('should be able to generate a group element symmetric key', function() {
      cryptolib(
          'commons.mathematical', 'primitives.securerandom', function(box) {
            var zpSubgroup =
                box.commons.mathematical.groupUtils.buildZpSubgroupFromPAndG(
                    _p, _g);

            var secureRandomFactory =
                new box.primitives.securerandom.factory.SecureRandomFactory();
            var cryptoRandomInteger =
                secureRandomFactory.getCryptoRandomInteger();

            for (var i = 0; i < 100; i++) {
              var groupElementSymmetricKey =
                  box.commons.mathematical.groupUtils
                      .generateGroupElementSecretKey(
                          zpSubgroup, cryptoRandomInteger);
              expect(groupElementSymmetricKey).toBeDefined();
              expect(zpSubgroup.isGroupMember(groupElementSymmetricKey))
                  .toBeTruthy();
            }
          });
    });

    it('should be able to build a ZpSubgroup, given a p parameter and a generator',
       function() {
         cryptolib('commons.mathematical', function(box) {
           var zpSubgroup =
               new box.commons.mathematical.groupUtils.buildZpSubgroupFromPAndG(
                   _p, _g);
           expect(zpSubgroup).toBeDefined();

           var undefinedP;
           var undefinedGenerator;
           expect(function() {
             box.commons.mathematical.groupUtils.buildZpSubgroupFromPAndG(
                 undefinedP, undefinedGenerator);
           }).toThrow();

           expect(function() {
             box.commons.mathematical.groupUtils.buildZpSubgroupFromPAndG(1, 2);
           }).toThrow();
         });
       });

    it('should be able to build a random ZpSubgroup, given a p parameter length',
       function() {
         cryptolib(
             'commons.mathematical', 'primitives.securerandom', function(box) {
               var secureRandomFactory = new box.primitives.securerandom.factory
                                             .SecureRandomFactory();
               var cryptoRandomBytes =
                   secureRandomFactory.getCryptoRandomBytes();
               var bitLengthOfP = 16;
               var certainty = box.MINIMUM_PRIME_CERTAINTY_LEVEL;
               var zpSubgroup =
                   new box.commons.mathematical.groupUtils
                       .buildRandomZpSubgroupFromLengthOfP(
                           bitLengthOfP, cryptoRandomBytes, certainty);

               expect(zpSubgroup).toBeDefined();
             });
       });

    it('should be able to extract the number of group members from a list of possible members',
       function() {
         cryptolib('commons.mathematical', function(box) {
           var possibleGroupMembersArray = [];
           for (var i = 0; i < GROUP_ELEMENT_VALUES_FOR_LARGE_ARRAY.length;
                i++) {
             possibleGroupMembersArray.push(new box.forge.jsbn.BigInteger(
                 '' + GROUP_ELEMENT_VALUES_FOR_LARGE_ARRAY[i]));
           }

           var numMembersRequired =
               GROUP_ELEMENT_VALUES_FOR_LARGE_ARRAY.length - 2;

           var extractedGroupMembersArray =
               new box.commons.mathematical.groupUtils
                   .extractNumberOfGroupMembersFromListOfPossibleMembers(
                       possibleGroupMembersArray, _zpSubgroup,
                       numMembersRequired);
           expect(extractedGroupMembersArray).toBeDefined();
           expect(extractedGroupMembersArray.length).toBe(numMembersRequired);

           // throw an error when more members are required than are available.
           var excessNumMembersRequired =
               GROUP_ELEMENT_VALUES_FOR_LARGE_ARRAY.length + 1;
           expect(function() {
             box.commons.mathematical.groupUtils
                 .extractNumberOfGroupMembersFromListOfPossibleMembers(
                     possibleGroupMembersArray, _zpSubgroup,
                     excessNumMembersRequired);
           }).toThrow();

           // throw an error when arguments are undefined
           expect(function() {
             box.commons.mathematical.groupUtils
                 .extractNumberOfGroupMembersFromListOfPossibleMembers(
                     undefined, undefined, undefined);
           }).toThrow();

           // throw an error when array is empty
           expect(function() {
             box.commons.mathematical.groupUtils
                 .extractNumberOfGroupMembersFromListOfPossibleMembers(
                     [], _zpSubgroup, numMembersRequired);
           }).toThrow();

           // throw an error when the given array is not an array
           expect(function() {
             box.commons.mathematical.groupUtils
                 .extractNumberOfGroupMembersFromListOfPossibleMembers(
                     1, _zpSubgroup, numMembersRequired);
           }).toThrow();
         });
       });

    it('should be able to divide two group element arrays', function() {
      cryptolib('commons.mathematical', function(box) {
        var value;
        var groupElement;
        var zpGroupElementsArray = [];

        value = new box.forge.jsbn.BigInteger('1');
        groupElement =
            new box.commons.mathematical.ZpGroupElement(value, _p, _q);
        zpGroupElementsArray.push(groupElement);

        value = new box.forge.jsbn.BigInteger('2');
        groupElement =
            new box.commons.mathematical.ZpGroupElement(value, _p, _q);
        zpGroupElementsArray.push(groupElement);

        value = new box.forge.jsbn.BigInteger('16');
        groupElement =
            new box.commons.mathematical.ZpGroupElement(value, _p, _q);
        zpGroupElementsArray.push(groupElement);

        var dividedElementsArray =
            new box.commons.mathematical.groupUtils.divide(
                _zpGroupElementArray, zpGroupElementsArray, _zpSubgroup);

        expect(dividedElementsArray).toBeDefined();
      });
    });

    it('should be able to exponentiate a group element array', function() {
      cryptolib('commons.mathematical', function(box) {
        var exponentiatedElementArray =
            new box.commons.mathematical.groupUtils.exponentiateArrays(
                _zpGroupElementArray, _exponent, _zpSubgroup);

        expect(exponentiatedElementArray).toBeDefined();
        expect(exponentiatedElementArray.length)
            .toEqual(_zpGroupElementArray.length);
        expect(exponentiatedElementArray[0].getElementValue().toString())
            .toEqual('' + (Math.pow(_groupElement1Value, _exponentValue) % _p));
        expect(exponentiatedElementArray[1].getElementValue().toString())
            .toEqual('' + (Math.pow(_groupElement2Value, _exponentValue) % _p));
        expect(exponentiatedElementArray[2].getElementValue().toString())
            .toEqual('' + (Math.pow(_groupElement3Value, _exponentValue) % _p));

        expect(exponentiatedElementArray[0].getP()).toEqual(_p);
        expect(exponentiatedElementArray[1].getP()).toEqual(_p);
        expect(exponentiatedElementArray[2].getP()).toEqual(_p);

        expect(exponentiatedElementArray[0].getQ()).toEqual(_q);
        expect(exponentiatedElementArray[1].getQ()).toEqual(_q);
        expect(exponentiatedElementArray[2].getQ()).toEqual(_q);
      });
    });

    it('should honor the validation flag', function() {
      cryptolib('commons.mathematical', function(box) {
        var zpGroupElementArray = [];
        for (var i = 1; i < GROUP_ELEMENT_VALUES_FOR_LARGE_ARRAY.length; i++) {
          var numberString = GROUP_ELEMENT_VALUES_FOR_LARGE_ARRAY[i].toString();
          var value = new box.forge.jsbn.BigInteger(numberString);
          var groupElement =
              new box.commons.mathematical.ZpGroupElement(value, _p, _q);
          zpGroupElementArray.push(groupElement);
        }

        // Ensure group membership is not tested with no flag.
        var spy = spyOn(_zpSubgroup, 'isGroupMember');
        box.commons.mathematical.groupUtils.exponentiateArrays(
            zpGroupElementArray, _exponent, _zpSubgroup);
        expect(spy).toHaveBeenCalledTimes(0);

        // Ensure group membership is not tested with a false flag.
        box.commons.mathematical.groupUtils.exponentiateArrays(
            zpGroupElementArray, _exponent, _zpSubgroup, false);
        expect(spy).toHaveBeenCalledTimes(0);

        // The function should crash, as the array contains elements that do not
        // belong to the group.
        expect(function() {
          box.commons.mathematical.groupUtils.exponentiateArrays(
              zpGroupElementArray, _exponent, _zpSubgroup, true);
        }).toThrow();
      });

    });

    describe(
        'Exponentiation of a ZpGroupElement array should throw an error ',
        function() {
          it(' when the base element is not from the current group',
             function() {
               cryptolib('commons.mathematical', function(box) {
                 var value = new box.forge.jsbn.BigInteger('19');
                 var groupElement =
                     new box.commons.mathematical.ZpGroupElement(value, _p, _q);
                 var zpGroupElementArray = [];
                 zpGroupElementArray.push(groupElement);

                 expect(function() {
                   box.commons.mathematical.groupUtils.exponentiateArrays(
                       zpGroupElementArray, _exponent, _zpSubgroup, true);
                 }).toThrow();
               });
             });

          it('when the ZpGroupElement array is undefined', function() {
            cryptolib('commons.mathematical', function(box) {
              expect(function() {
                box.commons.mathematical.groupUtils.exponentiateArrays(
                    undefined, _exponent, _zpSubgroup);
              }).toThrow();
            });
          });

          it('when the ZpGroupElement array is empty', function() {
            cryptolib('commons.mathematical', function(box) {
              expect(function() {
                box.commons.mathematical.groupUtils.exponentiateArrays(
                    [], _exponent, _zpSubgroup);
              }).toThrow();
            });
          });

          it('when the Exponent is undefined', function() {
            cryptolib('commons.mathematical', function(box) {
              expect(function() {
                box.commons.mathematical.groupUtils.exponentiateArrays(
                    _zpGroupElementArray, undefined, _zpSubgroup);
              }).toThrow();
            });
          });

          it('when the Exponent is null', function() {
            cryptolib('commons.mathematical', function(box) {
              expect(function() {
                box.commons.mathematical.groupUtils.exponentiateArrays(
                    _zpGroupElementArray, null, _zpSubgroup);
              }).toThrow();
            });
          });

          it('when the ZpSubgroup is undefined', function() {
            cryptolib('commons.mathematical', function(box) {
              expect(function() {
                box.commons.mathematical.groupUtils.exponentiateArrays(
                    _zpGroupElementArray, _exponent, undefined);
              }).toThrow();
            });
          });

          it('when the ZpSubgroup is null', function() {
            cryptolib('commons.mathematical', function(box) {
              expect(function() {
                box.commons.mathematical.groupUtils.exponentiateArrays(
                    _zpGroupElementArray, _exponent, null);
              }).toThrow();
            });
          });
        });
  });
});
