/*
 * 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
 */
/*
 * 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
 */
/**
 * @global
 * @description This object describes the configuration scheme for this
 *              cryptographic library. The following modules are considered:
 *              <ul>
 *              <li> Asymmetric
 *              <li> Primitives
 *              <li> Stores
 *              <li> Symmetric
 *              <li> Digital Envelope
 *              </ul>
 * @readonly
 * @property {object} asymmetric.keypair - Policies for generating keypairs.
 * @property {object} asymmetric.keypair.encryption - Policies for generating
 *           encryption keypairs.
 * @property {object} asymmetric.keypair.encryption.algorithm - Algorithm.
 * @property {string} asymmetric.keypair.encryption.algorithm.RSA - RSA
 *           algorithm.
 * @property {object} asymmetric.keypair.encryption.provider - Provider.
 * @property {string} asymmetric.keypair.encryption.provider.FORGE - Forge
 *           provider.
 * @property {object} asymmetric.keypair.encryption.keyLength - Key length.
 * @property {number} asymmetric.keypair.encryption.keyLength.KL_2048 - 2048
 *           bits.
 * @property {number} asymmetric.keypair.encryption.keyLength.KL_3072 - 3072
 *           bits.
 * @property {number} asymmetric.keypair.encryption.keyLength.KL_4096 - 4096
 *           bits.
 * @property {object} asymmetric.keypair.encryption.publicExponent - Public
 *           exponent for generating encryption keypairs.
 * @property {number} asymmetric.keypair.encryption.publicExponent.F4 - 65537.
 *
 * @property {object} asymmetric.signer - Policies for signing and verifying
 *           data.
 * @property {object} asymmetric.signer.algorithm - Algorithm.
 * @property {string} asymmetric.signer.algorithm.RSA - RSA algorithm.
 * @property {object} asymmetric.signer.provider - Provider.
 * @property {string} asymmetric.signer.provider.FORGE - Forge provider.
 * @property {object} asymmetric.signer.hash - Hash algorithm.
 * @property {string} asymmetric.signer.hash.SHA256 - SHA-256 algorithm.
 * @property {object} asymmetric.signer.padding - Padding scheme.
 * @property {object} asymmetric.signer.padding.PSS - PSS padding scheme.
 * @property {string} asymmetric.signer.padding.PSS.name - PSS.
 * @property {object} asymmetric.signer.padding.PSS.hash - Hash for the PSS
 *           padding scheme.
 * @property {string} asymmetric.signer.padding.PSS.hash.SHA256 - SHA-256
 *           algorithm.
 * @property {object} asymmetric.signer.padding.PSS.mask - Mask generating
 *           function for the PSS padding scheme.
 * @property {object} asymmetric.signer.padding.PSS.mask.MGF1 - MGF1 for the PSS
 *           padding scheme.
 * @property {string} asymmetric.signer.padding.PSS.mask.MGF1.name - MGF1.
 * @property {object} asymmetric.signer.padding.PSS.mask.MGF1.hash - Hash
 *           algorithm for the MGF1 for the PSS padding scheme.
 * @property {string} asymmetric.signer.padding.PSS.mask.MGF1.hash.SHA256 -
 *           SHA-256 algorithm.
 * @property {object} asymmetric.signer.padding.PSS.saltLengthBytes - Salt
 *           length for the PSS padding scheme.
 * @property {number} asymmetric.signer.padding.PSS.saltLengthBytes.SL_32 - 32
 *           bytes.
 * @property {object} asymmetric.signer.publicExponent - Public exponent.
 * @property {number} asymmetric.signer.publicExponent.F4 - 65537.
 * @property {object} primitives.securerandom - Policies for generating
 *           securerandom.
 * @property {object} primitives.securerandom.provider - Provider.
 * @property {string} primitives.securerandom.provider.SCYTL - Scytl provider.
 * @property {object} primitives.messagedigest - Policies for generating message
 *           digests.
 * @property {object} primitives.messagedigest.algorithm - Algorithm.
 * @property {string} primitives.messagedigest.algorithm.SHA256 - SHA-256
 *           algorithm.
 * @property {object} primitives.messagedigest.provider - Provider.
 * @property {string} primitives.messagedigest.provider.FORGE - Forge provider.
 * @property {object} symmetric.secretkey - Policies to generate secret keys.
 * @property {number} symmetric.secretkey.length - 128 bits.
 * @property {object} symmetric.cipher - Policies to encrypt and decrypt
 *           symmetrically.
 * @property {object} symmetric.cipher.algorithm - Algorithm.
 * @property {object} symmetric.cipher.algorithm.AES128_GCM - AES128-GCM
 *           algorithm.
 * @property {string} symmetric.cipher.algorithm.AES128_GCM.name - AES128-GCM.
 * @property {number} symmetric.cipher.algorithm.AES128_GCM.keyLengthBytes - 16
 *           bytes.
 * @property {number} symmetric.cipher.algorithm.AES128_GCM.tagLengthBytes - 16
 *           bytes.
 * @property {object} symmetric.cipher.provider - Provider.
 * @property {object} symmetric.cipher.provider.FORGE - Forge provider.
 * @property {object} symmetric.cipher.initializationVectorLengthBytes -
 *           Initialization vector length.
 * @property {number} symmetric.cipher.initializationVectorLengthBytes.IV_12 -
 *           12 bytes.
 */
var Config = {
  homomorphic: {cipher: {secureRandom: {provider: {SCYTL: 'Scytl'}}}},
  asymmetric: {
    keypair: {
      encryption: {
        algorithm: {RSA: 'RSA'},
        provider: {FORGE: 'Forge'},
        keyLength: {KL_2048: 2048, KL_3072: 3072, KL_4096: 4096},
        publicExponent: {F4: 65537}
      },
      secureRandom: {provider: {SCYTL: 'Scytl'}}
    },
    signer: {
      algorithm: {RSA: 'RSA'},
      provider: {FORGE: 'Forge'},
      hash: {SHA256: 'SHA256'},
      padding: {
        PSS: {
          name: 'PSS',
          hash: {SHA256: 'SHA256'},
          mask: {MGF1: {name: 'MGF1', hash: {SHA256: 'SHA256'}}},
          saltLengthBytes: {SL_32: 32}
        }
      },
      publicExponent: {F4: 65537},
      secureRandom: {provider: {SCYTL: 'Scytl'}}
    },
    cipher: {
      algorithm: {
        RSA_OAEP: {
          name: 'RSA-OAEP',
          hash: 'SHA256',
          mask: {MGF1: {name: 'MGF1', hash: 'SHA1'}}
        },
        RSA_KEM: {
          name: 'RSA-KEM',
          secretKeyLengthBytes: {KL_16: 16, KL_24: 24, KL_32: 32},
          ivLengthBytes: {IVL_12: 12, IVL_16: 16, IVL_32: 32, IVL_64: 64},
          tagLengthBytes: {TL_12: 12, TL_16: 16, TL_32: 32, TL_64: 64},
          deriver: {
            name: {KDF1: 'kdf1', KDF2: 'kdf2', MGF1: 'mgf1'},
            messagedigest: {algorithm: {SHA256: 'SHA256'}}
          },
          cipher: {AESGCM: 'AES-GCM'}
        }
      },
      provider: {FORGE: 'Forge'},
      secureRandom: {provider: {SCYTL: 'Scytl'}}
    }
  },
  primitives: {
    secureRandom: {provider: {SCYTL: 'Scytl'}},
    messagedigest: {
      algorithm: {SHA256: 'SHA256', SHA512_224: 'SHA512/224'},
      provider: {FORGE: 'Forge'}
    },
    derivation: {
      pbkdf: {
        provider: {FORGE: 'Forge'},
        hash: {SHA256: 'SHA256'},
        saltLengthBytes: {SL_20: 20, SL_32: 32},
        keyLengthBytes: {KL_16: 16, KL_32: 32},
        iterations: {
          I_1: 1,
          I_8000: 8000,
          I_16000: 16000,
          I_32000: 32000,
          I_64000: 64000
        }
      }
    }
  },
  stores: {},
  symmetric: {
    secretkey: {
      encryption: {lengthBytes: {SK_16: 16}},
      mac: {lengthBytes: {SK_32: 32}},
      secureRandom: {provider: {SCYTL: 'Scytl'}}
    },
    cipher: {
      provider: {FORGE: 'Forge'},
      algorithm: {
        AES128_GCM: {name: 'AES-GCM', keyLengthBytes: 16, tagLengthBytes: 16}
      },
      initializationVectorLengthBytes: {IV_12: 12},
      secureRandom: {provider: {SCYTL: 'Scytl'}}
    },
    mac: {hash: {SHA256: 'SHA256'}, provider: {FORGE: 'Forge'}}
  },
  proofs: {
    secureRandom: {provider: {SCYTL: 'Scytl'}},
    messagedigest: {
      algorithm: {SHA256: 'SHA256', SHA512_224: 'SHA512/224'},
      provider: {FORGE: 'Forge'}
    },
    charset: {UTF8: 'UTF-8'}
  },
  digitalenvelope: {
    symmetric: {
      secretkey: {
        encryption: {lengthBytes: {SK_16: 16}},
        mac: {lengthBytes: {SK_32: 32}},
        secureRandom: {provider: {SCYTL: 'Scytl'}}
      },
      cipher: {
        provider: {FORGE: 'Forge'},
        algorithm: {
          AES128_GCM:
              {name: 'AES-GCM', keyLengthBytes: 16, tagLengthBytes: 16}
        },
        initializationVectorLengthBytes: {IV_12: 12},
        secureRandom: {provider: {SCYTL: 'Scytl'}}
      },
      mac: {hash: {SHA256: 'SHA256'}, provider: {FORGE: 'Forge'}}
    },
    asymmetric: {
      cipher: {
        algorithm: {
          RSA_OAEP: {
            name: 'RSA-OAEP',
            hash: 'SHA256',
            mask: {MGF1: {name: 'MGF1', hash: 'SHA1'}}
          },
          RSA_KEM: {
            name: 'RSA-KEM',
            secretKeyLengthBytes: {KL_16: 16, KL_24: 24, KL_32: 32},
            ivLengthBytes: {IVL_12: 12, IVL_16: 16, IVL_32: 32, IVL_64: 64},
            tagLengthBytes: {TL_12: 12, TL_16: 16, TL_32: 32, TL_64: 64},
            deriver: {
              name: {KDF1: 'kdf1', KDF2: 'kdf2', MGF1: 'mgf1'},
              messagedigest: {algorithm: {SHA256: 'SHA256'}}
            },
            cipher: {AESGCM: 'AES-GCM'}
          }
        },
        provider: {FORGE: 'Forge'},
        secureRandom: {provider: {SCYTL: 'Scytl'}}
      }
    }
  }
};


if (Object.freeze) {
  /**
   * Freezes an object and all its contents.
   * @param {Object} target the object to freeze.
   */
  var recursiveFreeze = function(target) {
    'use strict';
    // Get all property names
    var propNames = Object.getOwnPropertyNames(target);

    // Freeze the object's properties.
    propNames.forEach(function(name) {
      var property = target[name];

      // Freeze the properties' properties.
      if (typeof property === 'object' && property !== null) {
        recursiveFreeze(property);
      }
    });

    // Freeze the object itself.
    return Object.freeze(target);
  };

  // Lock down the configuration
  recursiveFreeze(Config);
}


var cryptolibPolicies;

/**
 * @global
 * @function cryptolib
 *
 * @description Core of the library, this module is used to sandbox the
 *              requested modules.
 *
 * @example // how to load all modules cryptolib('*', function(box) { // any
 *          module can be used from here. //
 *          box.examplemodule.service.getExample(); });
 *
 *
 * @param modules
 *            {...modules} modules to be loaded. '*' or none to load all the
 *            modules.
 * @param function
 *            {cryptolibSandbox} a closure for the cryptolib object.
 * @tutorial workers
 */
function cryptolib() {
  'use strict';

  /* Class to keep the references to polices and prng. */
  function Cryptolib(modules, callback) {
    function getAllModules(parent) {
      var modules = [];
      for (var module in parent) {
        if (parent.hasOwnProperty(module)) {
          modules.push(module);
        }
      }
      return modules;
    }

    function loadModules(box, modules, parentModule) {
      if (!modules || modules.toString() === '*'.toString()) {
        modules = getAllModules(parentModule);
      }

      for (var i = 0; i < modules.length; i += 1) {
        var firstDotIndex = modules[i].indexOf('.');
        if (firstDotIndex > -1) {
          var subModule = modules[i].substr(firstDotIndex + 1);
          var root = modules[i].substr(0, firstDotIndex);

          loadModules(box, [subModule], parentModule[root]);
        } else if (typeof parentModule[modules[i]] === 'function') {
          parentModule[modules[i]](box);
        } else if (typeof parentModule[modules[i]] === 'object') {
          loadModules(box, '*', parentModule[modules[i]]);
        }
      }
    }

    this.policies = callback.policies || cryptolibPolicies;
    cryptolibPolicies = cryptolibPolicies || this.policies;
    this.prng = callback.prng || cryptoPRNG.getPRNG();
    loadModules(this, modules, cryptolib.modules);
    callback(this);
  }

  Cryptolib.prototype = {
    getKeys: function(obj) {
      var keys = [];
      for (var key in obj) {
        if (typeof key !== 'undefined') {
          keys.push(key);
        }
      }
      return keys;
    },

    getModuleNames: function() {
      var moduleNames = '';
      for (var module in cryptolib.modules) {
        if (typeof module !== 'undefined') {
          if (moduleNames.length > 0) {
            moduleNames += ', ';
          }
          moduleNames += module;
        }
      }

      return '[' + moduleNames + ']';
    },
    forge: forge,  // redefines forge global variable
    sjcl: sjcl,    // redefines sjcl global variable
    // Constants.
    /** @constant */
    NUMBER_OF_BITS_PER_BYTE: 8,
    /** @constant */
    MAXIMUM_LENGTH_TO_GENERATE_DATA: 512,
    /** @constant */
    BASE_2_RADIX: 2,
    /** @constant */
    BASE_10_RADIX: 10,
    /** @constant */
    BASE_16_RADIX: 16,
    /** @constant */
    EPOCH_TIME_LENGTH: 10,
    /** @constant */
    RSA_LINE_LENGTH: 64,
    /** @constant */
    SHA_256_LENGTH: 32,
    /** @constant */
    HMAC_SHA256_KEY_LENGTH: 32,
    /** @constant */
    AES_KEY_LENGTH: 16,
    /** @constant */
    AES_CBC_IV_LENGTH: 16,
    /** @constant */
    DES3_KEY_LENGTH: 24,
    /** @constant */
    DES3_CBC_IV_LENGTH: 8,
    EQUALS: ' = ',
    NEWLINE: '\n',
    CRYPTO_SPEC_DELIM: '/',
    /** @constant */
    SHA_256: 'SHA256',
    /** @constant */
    RSA_ECB_PKCS1PADDING: 'RSA/ECB/PKCS1PADDING',
    /** @constant */
    RSA_ECB_OAEP_WITH_SHA256_AND_MGF1PADDING:
        'RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING',
    /** @constant */
    FORGE_RSA_OAEP: 'RSA-OAEP',
    /** @constant */
    FORGE_RSAES_PKCS1_V1_5: 'RSAES-PKCS1-V1_5',
    /** @constant */
    MINIMUM_PRIME_CERTAINTY_LEVEL: 80,
    /** @constant */
    SHORT_EXPONENT_BIT_LENGTH: 256,
    /** @constant */
    EXPONENTIATION_PROOF_AUXILIARY_DATA: 'ExponentiationProof',
    /** @constant */
    PLAINTEXT_PROOF_AUXILIARY_DATA: 'PlaintextProof',
    /** @constant */
    SIMPLE_PLAINTEXT_EQUALITY_PROOF_AUXILIARY_DATA:
        'SimplePlaintextEqualityProof',
    /** @constant */
    PLAINTEXT_EQUALITY_PROOF_AUXILIARY_DATA: 'PlaintextEqualityProof',
    /** @constant */
    PLAINTEXT_EXPONENT_EQUALITY_PROOF_AUXILIARY_DATA:
        'PlaintextExponentEqualityProof'
  };

  var args = Array.prototype.slice.call(arguments);
  var callback = args.pop();
  var modules = (args[0] && typeof args[0] === 'string') ? args : args[0];

  return new Cryptolib(modules, callback);
}

cryptolib.modules = {};

/*
 * 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
 */
////////////////////////////////////////////////
//
// Functionalities added to JavaScript types
//
////////////////////////////////////////////////

/**
 * An equals method for arrays.
 *
 * @param array
 *            the array to which this array should be compared.
 * @param strict
 *            a boolean value which specifies whether or not the elements must
 *            be in the same order in both arrays. This parameter is optional,
 *            if it is not supplied, then a default value of 'true' is assigned
 *            to it.
 */
Array.prototype.equals = function(array, strict) {
  'use strict';

  if (!array) {
    return false;
  }

  if (arguments.length === 1) {
    strict = true;
  }

  if (this.length !== array.length) {
    return false;
  }

  for (var i = 0; i < this.length; i++) {
    if (this[i] instanceof Array && array[i] instanceof Array) {
      if (!this[i].equals(array[i], strict)) {
        return false;
      }
    } else if (strict && (!this[i].equals(array[i]))) {
      return false;
    } else if (!strict) {
      return this.sort().equals(array.sort(), true);
    }
  }
  return true;
};

/**
 * Allows all of the elements of one array to be added into another array.
 */
Array.prototype.addAll = function() {
  'use strict';

  for (var a = 0; a < arguments.length; a++) {
    var arr = arguments[a];
    for (var i = 0; i < arr.length; i++) {
      this.push(arr[i]);
    }
  }
};

/*
 * 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
 */
cryptolib.modules.asymmetric = cryptolib.modules.asymmetric || {};

/**
 * @namespace asymmetric/cipher
 */
cryptolib.modules.asymmetric.cipher = function(box) {
  'use strict';

  box.asymmetric = box.asymmetric || {};
  box.asymmetric.cipher = {};

  /**
   * A module that holds asymmetric cipher functionalities.
   *
   * @exports asymmetric/cipher/factory
   */
  box.asymmetric.cipher.factory = {};

  var policies = {
    cipher: {
      algorithm: box.policies.asymmetric.cipher.algorithm,
      secretKeyLengthBytes: box.policies.asymmetric.cipher.secretKeyLengthBytes,
      ivLengthBytes: box.policies.asymmetric.cipher.ivLengthBytes,
      tagLengthBytes: box.policies.asymmetric.cipher.tagLengthBytes,
      deriver: box.policies.asymmetric.cipher.deriver,
      hash: box.policies.asymmetric.cipher.hash,
      symmetricCipher: box.policies.asymmetric.cipher.symmetricCipher,
      provider: box.policies.asymmetric.cipher.provider
    }
  };

  var utils, converters, exceptions, randomFactory;

  var f = function(box) {
    utils = box.commons.utils;
    converters = new box.commons.utils.Converters();
    exceptions = box.commons.exceptions;
    randomFactory =
        new box.primitives.securerandom.factory.SecureRandomFactory();
  };

  f.policies = {
    primitives: {
      secureRandom:
          {provider: box.policies.asymmetric.cipher.secureRandom.provider}
    }
  };

  cryptolib('commons', 'primitives.securerandom', f);
  /** @class */
  box.asymmetric.cipher.factory.AsymmetricCipherFactory = function() {};

  box.asymmetric.cipher.factory.AsymmetricCipherFactory.prototype = {

    /**
     * @function Gets an asymmetric cipher.
     * @returns {asymmetric/cipher.CryptoForgeAsymmetricCipher}
     */
    getCryptoAsymmetricCipher: function() {
      try {
        if (policies.cipher.provider ===
            Config.asymmetric.cipher.provider.FORGE) {
          var secureRandomBytes = randomFactory.getCryptoRandomBytes();
          return this.getCryptoForgeAsymmetricCipher(secureRandomBytes);
        } else {
          throw new exceptions.CryptoLibException(
              'No suitable provider for the asymmetric cipher was provided.');
        }
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'A CryptoAsymmetricCipher could not be obtained.', error);
      }
    },

    /**
     * @function Gets a Forge asymmetric cipher.
     * @param secureRandom
     *            {CryptoScytlRandomBytes} a source of random bytes.
     * @returns {asymmetric/cipher.CryptoForgeAsymmetricCipher}
     */
    getCryptoForgeAsymmetricCipher: function(secureRandom) {
      return new CryptoForgeAsymmetricCipher(secureRandom);
    }
  };

  /**
   * Defines a Forge asymmetric cipher
   *
   * @class
   * @param secureRandomBytes
   *            {CryptoScytlRandomBytes} a source of random bytes.
   * @memberof asymmetric/cipher
   */
  function CryptoForgeAsymmetricCipher(secureRandomBytes) {
    if (!secureRandomBytes) {
      throw new exceptions.CryptoLibException(
          'The received PRNG was not valid');
    }

    this.bitOperators = new utils.BitOperators();

    this.secureRandomBytes = secureRandomBytes;

    // We would like to use the SCYTL PRNG directly in the ciphers that are
    // created. However it is currently not possible to pass a PRNG to the
    // Forge library when creating or using a cipher. However, in some cases,
    // it is possible to pass a seed (created using the SCYTL PRNG) to FORGE
    // that is then used to seed the FORGE internal PRNG.
    this.encodingSeedLength = 32;

    try {
      if ((policies.cipher.algorithm.name !==
           Config.asymmetric.cipher.algorithm.RSA_OAEP.name) &&
          (policies.cipher.algorithm.name !==
           Config.asymmetric.cipher.algorithm.RSA_KEM.name)) {
        throw new exceptions.CryptoLibException(
            'The specified algorithm is not supported.');
      }
    } catch (error) {
      throw new exceptions.CryptoLibException(
          'CryptoForgeAsymmetricCipher could not be created.', error);
    }

    /**
     * Parses the four parts of the data that is produced by the encrypt
     * function, and that is received by the decrypt function.
     * <p>
     * We know the length of three of these parts, and we know the total
     * length of the data, therefore we can parse out all four of the parts.
     */
    this._parseParts = function(privateKey, encryptedData) {

      var ivLengthBytes = policies.cipher.ivLengthBytes;
      var tagLengthBytes = policies.cipher.tagLengthBytes;
      var encapsulationLengthBytes = privateKey.n.bitLength() / 8;

      var totalLength = encryptedData.length;
      var totalKnownLength =
          encapsulationLengthBytes + ivLengthBytes + tagLengthBytes;
      var encryptedDataLength = totalLength - totalKnownLength;

      var startIndexOfSecondPart = encapsulationLengthBytes;
      var startIndexOfThirdPart = startIndexOfSecondPart + ivLengthBytes;
      var startIndexOfFourthPart = startIndexOfThirdPart + encryptedDataLength;

      var encapsulation =
          this.bitOperators.extract(encryptedData, 0, startIndexOfSecondPart);

      var iv = this.bitOperators.extract(
          encryptedData, startIndexOfSecondPart, startIndexOfThirdPart);

      var data = this.bitOperators.extract(
          encryptedData, startIndexOfThirdPart, startIndexOfFourthPart);

      var tag =
          this.bitOperators.extract(encryptedData, startIndexOfFourthPart);

      return {encapsulation: encapsulation, iv: iv, data: data, tag: tag};
    };

    this._validateInputs = function(keyPem, data) {

      if (!keyPem) {
        throw new exceptions.CryptoLibException(
            'The received key was not initialized.');
      }

      if (!data) {
        throw new exceptions.CryptoLibException(
            'The received data was not initialized.');
      }
    };

    // Note: at the moment, the options that are being used with the RSA_OAEP
    // algorithm are hardcoded (for encrypting and decrypting). This could be
    // improved so that these are read from the properties file. Doing this will
    // mean any that existing properties files (used by consumers of the
    // library) will become invalid (if the consumer uses RSA_OAEP) as they wont
    // have the mandatory new properties.
    this._getRsaOaepHash = function() {
      return box.forge.md.sha256.create();
    };

    //
    this._getRsaOaepMaskHash = function() {
      // For interoperability purposes, the MGF1 hash function must
      // remain as SHA-1.
      return box.forge.md.sha1.create();
    };

    this._getRsaOaepEncodingOptions = function() {

      var encodingOptions = {
        md: this._getRsaOaepHash(),
        mgf1: {md: this._getRsaOaepMaskHash()},
        seed: this.secureRandomBytes.nextRandom(this.encodingSeedLength)
      };

      return encodingOptions;
    };

    this._getRsaOaepDecodingOptions = function() {

      var decodingOptions = {
        md: this._getRsaOaepHash(),
        mgf1: {md: this._getRsaOaepMaskHash()}
      };

      return decodingOptions;
    };

    this._determineAndCreateHash = function(requestedHash) {

      if (requestedHash ===
          Config.asymmetric.cipher.algorithm.RSA_KEM.deriver.messagedigest
              .algorithm.SHA256) {
        return forge.md.sha256.create();
      } else {
        throw new exceptions.CryptoLibException(
            'Unsupported hash function specified.');
      }
    };

    this._determineAndCreateDeriver = function(requestedDeriver) {
      var hash = this._determineAndCreateHash(policies.cipher.hash);

      if (requestedDeriver ===
          Config.asymmetric.cipher.algorithm.RSA_KEM.deriver.name.KDF1) {
        return new forge.kem.kdf1(hash);
      } else if (
          requestedDeriver ===
          Config.asymmetric.cipher.algorithm.RSA_KEM.deriver.name.KDF2) {
        return new forge.kem.kdf2(hash);
      } else if (
          requestedDeriver ===
          Config.asymmetric.cipher.algorithm.RSA_KEM.deriver.name.MGF1) {
        return new forge.mgf.mgf1.create(hash);
      } else {
        throw new exceptions.CryptoLibException(
            'Unsupported deriver function specified.');
      }
    };
  }

  CryptoForgeAsymmetricCipher.prototype = {

    /**
     * Encrypts some data.
     * <p>
     * If the algorithm is RSA-KEM, then the output from this function will
     * be the base64 encoding of the following data:
     * <p>
     * [Encapsulation][IV][Encrypted Data][Tag]
     *
     * @function
     * @param publicKeyPem
     *            {string} public key, as string in PEM format.
     * @param dataBase64
     *            {string} data to be encrypted, as string in Base64 encoded
     *            format.
     * @returns encrypted data, as string in Base 64 encoded format..
     */
    encrypt: function(publicKeyPem, dataBase64) {
      this._validateInputs(publicKeyPem, dataBase64);

      try {
        var publicKey = box.forge.pki.publicKeyFromPem(publicKeyPem);

        var data = converters.base64Decode(dataBase64);

        var output;

        if (policies.cipher.algorithm.name ===
            Config.asymmetric.cipher.algorithm.RSA_OAEP.name) {
          var encodingOptions = this._getRsaOaepEncodingOptions();

          output = publicKey.encrypt(
              data, policies.cipher.algorithm.name, encodingOptions);

        } else if (
            policies.cipher.algorithm.name ===
            Config.asymmetric.cipher.algorithm.RSA_KEM.name) {
          var secretKeyLengthBytes = policies.cipher.secretKeyLengthBytes;
          var ivLengthBytes = policies.cipher.ivLengthBytes;
          var symmetricCipher = policies.cipher.symmetricCipher;

          var deriver =
              this._determineAndCreateDeriver(policies.cipher.deriver);

          // generate and encapsulate secret key
          var kem = forge.kem.rsa.create(deriver);
          var result = kem.encrypt(publicKey, secretKeyLengthBytes);

          var iv = forge.random.getBytesSync(ivLengthBytes);
          var cipher = forge.cipher.createCipher(symmetricCipher, result.key);
          cipher.start({iv: iv});
          cipher.update(forge.util.createBuffer(data));
          cipher.finish();

          var encryptedData = cipher.output.getBytes();
          var tag = cipher.mode.tag.getBytes();

          output = result.encapsulation.toString() + iv.toString() +
              encryptedData.toString() + tag.toString();

        } else {
          throw new exceptions.CryptoLibException(
              'The specified algorithm is not supported.');
        }

        return converters.base64Encode(output);
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'CryptoForgeAsymmetricCipher, data could not be encrypted.', error);
      }
    },

    /**
     * Decrypts some encrypted data.
     *
     * @function
     * @param privateKeyPem
     *            {string} private key, as string in PEM format.
     * @param encryptedDataB64
     *            {string} encrypted data, as string in Base64 encoded
     *            format.
     * @returns decrypted data, as string in Base64 encoded format.
     */
    decrypt: function(privateKeyPem, encryptedDataBase64) {

      this._validateInputs(privateKeyPem, encryptedDataBase64);

      try {
        var privateKey = box.forge.pki.privateKeyFromPem(privateKeyPem);

        var encryptedData = converters.base64Decode(encryptedDataBase64);

        var decryptedData;

        if (policies.cipher.algorithm.name ===
            Config.asymmetric.cipher.algorithm.RSA_OAEP.name) {
          var decodingOptions = this._getRsaOaepDecodingOptions();

          decryptedData = privateKey.decrypt(
              encryptedData, policies.cipher.algorithm.name, decodingOptions);

        } else if (
            policies.cipher.algorithm.name ===
            Config.asymmetric.cipher.algorithm.RSA_KEM.name) {
          var symmetricCipher = policies.cipher.symmetricCipher;
          var secretKeyLengthBytes = policies.cipher.secretKeyLengthBytes;

          var encryptedDataParts = this._parseParts(privateKey, encryptedData);
          var deriver =
              this._determineAndCreateDeriver(policies.cipher.deriver);

          // decrypt encapsulated secret key
          var kem = forge.kem.rsa.create(deriver);
          var key = kem.decrypt(
              privateKey, encryptedDataParts.encapsulation,
              secretKeyLengthBytes);

          // decrypt some bytes
          var decipher = forge.cipher.createDecipher(symmetricCipher, key);
          decipher.start(
              {iv: encryptedDataParts.iv, tag: encryptedDataParts.tag});
          decipher.update(forge.util.createBuffer(encryptedDataParts.data));
          var result = decipher.finish();

          // result will be false if there was a failure
          if (result) {
            decryptedData = decipher.output.getBytes();
          } else {
            throw new exceptions.CryptoLibException(
                'Error while decrypting data.');
          }
        } else {
          throw new exceptions.CryptoLibException(
              'The specified algorithm is not supported.');
        }

        return converters.base64Encode(decryptedData);
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'CryptoForgeAsymmetricCipher, data could not be decrypted.', error);
      }
    }
  };
};

/*
 * 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
 */
cryptolib.modules.asymmetric = cryptolib.modules.asymmetric || {};
/** @namespace asymmetric/keypair */
cryptolib.modules.asymmetric.keypair = function(box) {
  'use strict';

  box.asymmetric = box.asymmetric || {};
  box.asymmetric.keypair = {};

  /**
   * A module that holds certificates functionalities.
   *
   * @exports asymmetric/keypair/factory
   */
  box.asymmetric.keypair.factory = {};

  var policies = {
    encryption: {
      algorithm: box.policies.asymmetric.keypair.encryption.algorithm,
      provider: box.policies.asymmetric.keypair.encryption.provider,
      keyLength: box.policies.asymmetric.keypair.encryption.keyLength,
      publicExponent: box.policies.asymmetric.keypair.encryption.publicExponent
    }
  };

  var exceptions, randomFactory;

  var f = function(box) {
    exceptions = box.commons.exceptions;
    randomFactory =
        new box.primitives.securerandom.factory.SecureRandomFactory();
  };

  f.policies = {
    primitives: {
      secureRandom:
          {provider: box.policies.asymmetric.keypair.secureRandom.provider}
    }
  };

  cryptolib('commons.exceptions', 'primitives.securerandom', f);

  /**
   * A factory class for generating
   * {@link asymmetric.keypair.CryptoForgeKeyPairGenerator} objects.
   *
   * @class
   */
  box.asymmetric.keypair.factory.KeyPairGeneratorFactory = function() {};

  box.asymmetric.keypair.factory.KeyPairGeneratorFactory.prototype = {
    /**
     * @function
     * @returns CryptoForgeKeyPairGenerator
     */
    getEncryptionCryptoKeyPairGenerator: function() {
      var secureRandom;

      try {
        if (policies.encryption.provider ===
            Config.asymmetric.keypair.encryption.provider.FORGE) {
          secureRandom = randomFactory.getCryptoRandomBytes();

          return this.getCryptoForgeKeyPairGenerator(secureRandom);
        } else {
          throw new exceptions.CryptoLibException(
              'No suitable provider was provided.');
        }
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'An encryption CryptoKeyPairGenerator could not be obtained.',
            error);
      }
    },
    getCryptoForgeKeyPairGenerator: function(secureRandom) {
      return new CryptoForgeKeyPairGenerator(secureRandom);
    }
  };

  /**
   * Defines an encryption key pair generator for the FORGE provider.
   *
   * @class
   * @memberof asymmetric/keypair
   */
  function CryptoForgeKeyPairGenerator(secureRandom) {
    this.secureRandom = secureRandom;
  }

  CryptoForgeKeyPairGenerator.prototype = {

    /**
     * Generates an encryption key pair.
     *
     * @returns {asymmetric/keypair.KeyPair} an encryption key pair.
     */
    generate: function() {
      try {
        if (policies.encryption.algorithm ===
            Config.asymmetric.keypair.encryption.algorithm.RSA) {
          var options = {prng: this.secureRandom};

          var keys = forge.pki.rsa.generateKeyPair(
              policies.encryption.keyLength, policies.encryption.publicExponent,
              options);

          var forgePublicKey = keys.publicKey;
          var forgePrivateKey = keys.privateKey;

          var publicKeyPem = forge.pki.publicKeyToPem(forgePublicKey);
          var privateKeyPem = forge.pki.privateKeyToPem(forgePrivateKey);

          var publicKey = new PublicKey(
              policies.encryption.algorithm, forgePublicKey.n, forgePublicKey.e,
              publicKeyPem);

          var privateKey = new PrivateKey(
              policies.encryption.algorithm, forgePrivateKey.n,
              forgePrivateKey.e, forgePrivateKey.d, forgePrivateKey.p,
              forgePrivateKey.q, forgePrivateKey.dP, forgePrivateKey.dQ,
              forgePrivateKey.qInv, privateKeyPem);

          return new KeyPair(publicKey, privateKey);
        } else {
          throw new exceptions.CryptoLibException(
              'The given algorithm is not supported.');
        }
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'Encryption key pair could not be generated.', error);
      }
    }
  };

  /**
   * Container class for an encryption key pair.
   *
   * @class
   * @param publicKey
   *            public key of the encryption key pair.
   * @param privateKey
   *            private key of the encryption key pair.
   * @memberof asymmetric/keypair
   */
  function KeyPair(publicKey, privateKey) {
    /**
     * Retrieves the public key of the encryption key pair.
     *
     * @function
     * @returns PrivateKey Public key of encryption key pair.
     */
    var getPublicKey = function() {
      return publicKey;
    };

    /**
     * Retrieves the private key of the encryption key pair.
     *
     * @function
     * @returns PrivateKey Private key of encryption key pair.
     */
    var getPrivateKey = function() {
      return privateKey;
    };

    return {getPublicKey: getPublicKey, getPrivateKey: getPrivateKey};
  }

  /**
   * @class PrivateKey
   * @memberof asymmetric/keypair
   */
  function PrivateKey(algorithm, n, e, d, p, q, dP, dQ, qInv, pem) {
    /**
     * @function
     * @returns {string}
     * @memberof asymmetric/keypair.PrivateKey
     */
    var getAlgorithm = function() {
      return algorithm;
    };

    /**
     * @function
     * @memberof asymmetric/keypair.PrivateKey
     */
    var getModulus = function() {
      return n;
    };

    /**
     * @function
     * @memberof asymmetric/keypair.PrivateKey
     */
    var getPublicExponent = function() {
      return e;
    };

    /**
     * @function
     * @memberof asymmetric/keypair.PrivateKey
     */
    var getPrivateExponent = function() {
      return d;
    };

    /**
     * @function
     * @memberof asymmetric/keypair.PrivateKey
     */
    var getPrime1 = function() {
      return p;
    };

    /**
     * @function
     * @memberof asymmetric/keypair.PrivateKey
     */
    var getPrime2 = function() {
      return q;
    };

    /**
     * @function
     * @memberof asymmetric/keypair.PrivateKey
     */
    var getExponent1 = function() {
      return dP;
    };

    /**
     * @function
     * @memberof asymmetric/keypair.PrivateKey
     */
    var getExponent2 = function() {
      return dQ;
    };

    /**
     * @function
     * @memberof asymmetric/keypair.PrivateKey
     */
    var getCoefficient = function() {
      return qInv;
    };

    /**
     * @function
     * @memberof asymmetric/keypair.PrivateKey
     */
    var getPem = function() {
      return pem;
    };

    return {
      getAlgorithm: getAlgorithm,
      getModulus: getModulus,
      getPublicExponent: getPublicExponent,
      getPrivateExponent: getPrivateExponent,
      getPrime1: getPrime1,
      getPrime2: getPrime2,
      getExponent1: getExponent1,
      getExponent2: getExponent2,
      getCoefficient: getCoefficient,
      getPem: getPem
    };
  }

  /** @class PublicKey */
  function PublicKey(algorithm, n, e, pem) {
    /**
     * @function
     * @memberof asymmetric/keypair.PublicKey
     */
    var getAlgorithm = function() {
      return algorithm;
    };

    /**
     * @function
     * @memberof asymmetric/keypair.PublicKey
     */
    var getModulus = function() {
      return n;
    };

    /**
     * @function
     * @memberof asymmetric/keypair.PublicKey
     */
    var getPublicExponent = function() {
      return e;
    };

    /**
     * @function
     * @memberof asymmetric/keypair.PublicKey
     */
    var getPem = function() {
      return pem;
    };

    return {
      getAlgorithm: getAlgorithm,
      getModulus: getModulus,
      getPublicExponent: getPublicExponent,
      getPem: getPem
    };
  }
};

/*
 * 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
 */
cryptolib.modules.asymmetric = cryptolib.modules.asymmetric || {};

cryptolib.modules.asymmetric.service = function(box) {
  'use strict';

  box.asymmetric = box.asymmetric || {};
  /**
   * A module that holds certificates functionalities.
   * @exports asymmetric/service
   */
  box.asymmetric.service = {};

  var keypairGeneratorFactory, signerFactory, xmlSignerFactory,
      cipherFactory;

  cryptolib(
      'asymmetric.signer', 'asymmetric.xmlsigner', 'asymmetric.keypair',
      'asymmetric.cipher', 'commons.exceptions', function(box) {
        keypairGeneratorFactory =
            new box.asymmetric.keypair.factory.KeyPairGeneratorFactory();
        signerFactory = new box.asymmetric.signer.factory.SignerFactory();
        xmlSignerFactory =
            new box.asymmetric.xmlsigner.factory.XmlSignerFactory();
        cipherFactory =
            new box.asymmetric.cipher.factory.AsymmetricCipherFactory();
      });

  /**
   * Generates a {@link asymmetric/keypair.KeyPair} to be used for encrypt data.
   * @function
   * @returns {asymmetric/keypair.KeyPair} it can be used for encrypt data.
   */
  box.asymmetric.service.getKeyPairForEncryption = function() {
    var cryptoKeyPairGenerator =
        keypairGeneratorFactory.getEncryptionCryptoKeyPairGenerator();
    return cryptoKeyPairGenerator.generate();
  };

  /**
   * Encrypts the given plain text using the given
   * public key.
   * @function
   * @param publicKeyPem
   *            {String} public key, as string in PEM format.
   * @param dataBase64
   *            {String} data to encrypt, as a string in Base64 encoded
   *            format.
   * @return encrypted data in Base64 encoded format.
   */
  box.asymmetric.service.encrypt = function(publicKeyPem, dataBase64) {
    var cipher = cipherFactory.getCryptoAsymmetricCipher();
    return cipher.encrypt(publicKeyPem, dataBase64);
  };

  /**
   * Decrypts the given cipher text with the given
   * private key.
   * @function
   * @param privateKeyPem
   *            {String} private key, as string in PEM format.
   * @param encryptedDataBase64
   *            {String} data to decrypt, as a string in Base64 encoded
   *            format.
   * @return a string in Base64 encoded format.
   */
  box.asymmetric.service.decrypt = function(
      privateKeyPem, encryptedDataBase64) {
    var cipher = cipherFactory.getCryptoAsymmetricCipher();
    return cipher.decrypt(privateKeyPem, encryptedDataBase64);
  };

  /**
   * Signs the given message using the given private key.
   * @function
   * @param privateKeyPem
   *            {String} private key, as string in PEM format.
   * @param arrayDataBase64
   *            {String} data to sign, as an array of string in Base64 encoded
   *            format.
   * @return signature, in Base64 encoded format.
   */
  box.asymmetric.service.sign = function(privateKeyPem, ArrayDataBase64) {
    var cryptoSigner = signerFactory.getCryptoSigner();
    return cryptoSigner.sign(privateKeyPem, ArrayDataBase64);
  };

  /**
   * Verifies that the given signature is indeed the signature of the given
   * bytes, using the given public key.
   * @function
   * @param signatureB64
   *            {String} signature, as string in Base64 encoded format.
   * @param publicKeyPem
   *            {String} public key, as string in PEM format.
   * @param arrayDataBase64
   *            {String} data that was signed, as an array of string in Base64
   *            encoded format.
   *
   * @return boolean indicating whether signature verification was successful.
   */
  box.asymmetric.service.verifySignature = function(
      signatureBase64, publicKeyPem, arrayDataBase64) {
    var cryptoSigner = signerFactory.getCryptoSigner();
    return cryptoSigner.verify(signatureBase64, publicKeyPem, arrayDataBase64);
  };

  /**
   * Verifies the signature of some data that is in XML format.
   * @function
   * @param publicKey
   *            {Object} Public key.
   * @param signedXml
   *            {string} XML with selfcontained signature, to verify
   * @param signatureParentNode
   *            {string} Node where the signature is
   *
   * @return boolean indicating whether signature verification was successful.
   */
  box.asymmetric.service.verifyXmlSignature = function(
      publicKey, signedXml, signatureParentNode) {
    var cryptoXmlSigner = xmlSignerFactory.getCryptoXmlSigner();
    return cryptoXmlSigner.verify(publicKey, signedXml, signatureParentNode);
  };
};

/*
 * 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
 */
cryptolib.modules.asymmetric = cryptolib.modules.asymmetric || {};
/** @namespace asymmetric/signer */
cryptolib.modules.asymmetric.signer = function(box) {
  'use strict';

  box.asymmetric = box.asymmetric || {};
  box.asymmetric.signer = {};

  /**
   * A module that holds asymmetric signature functionalities.
   *
   * @exports asymmetric/signer/factory
   */
  box.asymmetric.signer.factory = {};

  var policies = {
    algorithm: box.policies.asymmetric.signer.algorithm,
    provider: box.policies.asymmetric.signer.provider,
    hash: box.policies.asymmetric.signer.hash,
    padding: box.policies.asymmetric.signer.padding,
    publicExponent: box.policies.asymmetric.signer.publicExponent
  };

  var converters, exceptions, randomFactory;

  var f = function(box) {
    converters = new box.commons.utils.Converters();
    exceptions = box.commons.exceptions;
    randomFactory =
        new box.primitives.securerandom.factory.SecureRandomFactory();
  };

  f.policies = {
    primitives: {
      secureRandom:
          {provider: box.policies.asymmetric.signer.secureRandom.provider}
    }
  };

  cryptolib('commons', 'primitives.securerandom', f);

  /**
   * A factory class for creating a digital signer.
   *
   * @class
   */
  box.asymmetric.signer.factory.SignerFactory = function() {};

  box.asymmetric.signer.factory.SignerFactory.prototype = {
    getCryptoSigner: function() {
      try {
        if (policies.provider === Config.asymmetric.signer.provider.FORGE) {
          var secureRandom = randomFactory.getCryptoRandomBytes();

          return this.getCryptoForgeSigner(secureRandom);
        } else {
          throw new exceptions.CryptoLibException(
              'No suitable provider for the signer was provided.');
        }
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'A CryptoSigner could not be obtained.', error);
      }
    },
    /**
     * @function
     * @return {asymmetric/signer.CryptoForgeSigner}
     */
    getCryptoForgeSigner: function(secureRandom) {
      return new CryptoForgeSigner(secureRandom);
    }
  };

  /**
   * A digital signer.
   *
   * @class
   * @memberof asymmetric/signer
   * @param secureRandom
   */
  function CryptoForgeSigner(secureRandom) {
    this.digester = null;
    this.secureRandom = secureRandom;

    try {
      if (policies.algorithm !== Config.asymmetric.signer.algorithm.RSA) {
        throw new exceptions.CryptoLibException(
            'The given algorithm is not supported');
      }

      if (policies.hash === Config.asymmetric.signer.hash.SHA256) {
        this.digester = forge.md.sha256.create();
      } else {
        var errorMessage = 'Message digester type \'' + policies.hash +
            '\' not recognized by signer.';
        throw new exceptions.CryptoLibException(errorMessage);
      }

      if (typeof(policies.padding) !== 'undefined' && 
        policies.padding.PSS.name === Config.asymmetric.signer.padding.PSS.name) {
        var paddingHash;
        if (policies.padding.PSS.hash ===
            Config.asymmetric.signer.padding.PSS.hash.SHA256) {
          paddingHash = forge.md.sha256.create();
        }

        var paddingMgf;
        if (policies.padding.PSS.mask.MGF1.name ===
            Config.asymmetric.signer.padding.PSS.mask.MGF1.name &&
            policies.padding.PSS.mask.MGF1.hash ===
            Config.asymmetric.signer.padding.PSS.mask.MGF1.hash.SHA256) {
          var mgfHash = forge.md.sha256.create();
          paddingMgf = forge.mgf.mgf1.create(mgfHash);
        }

        this.padding = forge.pss.create({
          md: paddingHash,
          mgf: paddingMgf,
          saltLength: policies.padding.PSS.saltLengthBytes,
          prng: this.secureRandom
        });
      }
    } catch (error) {
      throw new exceptions.CryptoLibException(
          'CryptoForgeSigner could not be created.', error);
    }
  }

  CryptoForgeSigner.prototype = {
    /**
     * Digitally signs some data.
     *
     * @function
     * @param privateKeyPem
     *            Private key, as string in PEM format.
     * @param arrayDataBase64
     *            Data to sign, as an array of string in Base64 encoded
     *            format.
     * @returns signature, in Base64 encoded format.
     */
    sign: function(privateKeyPem, arrayDataBase64) {
      try {
        var privateKey = forge.pki.privateKeyFromPem(privateKeyPem);

        if (privateKey.e.toString() !== policies.publicExponent.toString()) {
          throw new exceptions.CryptoLibException(
              'Private key has not been generated with the required exponent : ' +
              policies.publicExponent);
        }

        if (!arrayDataBase64 || arrayDataBase64.length < 1) {
          throw new exceptions.CryptoLibException(
              'The array of data in Base64 should contain at least one element.');
        }

        this.digester.start();
        var dataB64;
        var data;
        for (var i = 0; i < arrayDataBase64.length; i++) {
          dataB64 = arrayDataBase64[i];

          if (dataB64 === undefined || dataB64 === null) {
            throw new exceptions.CryptoLibException(
                'The input data contained an element that was not defined or empty.');
          }

          data = converters.base64Decode(dataB64);
          this.digester.update(data);
        }

        var signature = privateKey.sign(this.digester, this.padding);

        return converters.base64Encode(signature);
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'Signature could not be generated.', error);
      }
    },

    /**
     * Verifies the digital signature of some data.
     *
     * @function
     *
     * @param signatureB64
     *            Signature, as string in Base64 encoded format.
     * @param publicKeyPem
     *            Public key, as string in PEM format.
     * @param arrayDataBase64
     *            Data that was signed, as an array of string in Base64
     *            encoded format.
     *
     * @returns boolean indicating whether signature verification was
     *          successful.
     */
    verify: function(signatureB64, publicKeyPem, arrayDataBase64) {
      try {
        var publicKey = forge.pki.publicKeyFromPem(publicKeyPem);

        if (publicKey.e.toString() !== policies.publicExponent.toString()) {
          throw new exceptions.CryptoLibException(
              'Public key has not been generated with the required exponent : ' +
              policies.publicExponent);
        }

        if (!arrayDataBase64 || arrayDataBase64.length < 1) {
          throw new exceptions.CryptoLibException(
              'The array of data in Base64 should contain at least one element.');
        }

        this.digester.start();
        var dataB64;

        for (var i = 0; i < arrayDataBase64.length; i++) {
          dataB64 = arrayDataBase64[i];

          if (dataB64 === undefined || dataB64 === null) {
            throw new exceptions.CryptoLibException(
                'The input data contained an element that was not defined or empty.');
          }

          var data = converters.base64Decode(dataB64);
          this.digester.update(data);
        }

        var signature = converters.base64Decode(signatureB64);

        return publicKey.verify(
            this.digester.digest().getBytes(), signature, this.padding);
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'Signature could not be verified.', error);
      }
    }
  };
};

/*
 * 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
 */
cryptolib.modules.asymmetric = cryptolib.modules.asymmetric || {};

/**
 * Defines the asymmetric signers.
 *
 * @namespace asymmetric/xmlsigner
 */
cryptolib.modules.asymmetric.xmlsigner = function(box) {
  'use strict';

  box.asymmetric = box.asymmetric || {};
  box.asymmetric.xmlsigner = {};

  /**
   * A module that holds certificates functionalities.
   *
   * @exports asymmetric/xmlsigner/factory
   */
  box.asymmetric.xmlsigner.factory = {};

  var policies = {
    algorithm: box.policies.asymmetric.signer.algorithm,
    provider: box.policies.asymmetric.signer.provider,
    hash: box.policies.asymmetric.signer.hash
  };

  var utils, exceptions, converters, parsers, stringUtils;

  var f = function(box) {
    exceptions = box.commons.exceptions;
    utils = box.commons.utils;
    converters = new box.commons.utils.Converters();
    parsers = new utils.Parsers();
    stringUtils = new utils.StringUtils();
  };

  cryptolib('commons', f);

  /**
   * A factory class for creating an XML digital signature verifier.
   *
   * @class
   */
  box.asymmetric.xmlsigner.factory.XmlSignerFactory = function() {};

  box.asymmetric.xmlsigner.factory.XmlSignerFactory.prototype = {
    /**
     * Gets a {@link asymmetric/xmlsigner.CryptoForgeXmlSigner}.
     *
     * @function
     * @return {asymmetric/xmlsigner.CryptoForgeXmlSigner}
     */
    getCryptoXmlSigner: function() {
      try {
        if (policies.provider === Config.asymmetric.signer.provider.FORGE) {
          return new CryptoForgeXmlSigner();
        } else {
          throw new exceptions.CryptoLibException(
              'No suitable provider for the signer was provided.');
        }
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'A CryptoXmlSigner could not be obtained.', error);
      }
    }
  };

  /**
   * Holds xml signature properties.
   *
   * @class
   * @memberof asymmetric/xmlsigner
   */
  function XmlSignature(signatureNode) {
    // go through all the nodes to load all relevant information
    var signedInfoNode;
    var signatureValueNode;
    var canonicalizationMethodNode;
    var signatureMethodNode;
    var referenceNode;
    var digestMethodNode;
    var digestValueNode;
    var transformsNode;
    try {
      signedInfoNode = new utils.XPath(signatureNode.getXml(), 'SignedInfo');
      signatureValueNode =
          new utils.XPath(signatureNode.getXml(), 'SignatureValue');

      canonicalizationMethodNode =
          new utils.XPath(signedInfoNode.getXml(), 'CanonicalizationMethod');
      signatureMethodNode =
          new utils.XPath(signedInfoNode.getXml(), 'SignatureMethod');
      referenceNode = new utils.XPath(signedInfoNode.getXml(), 'Reference');

      digestMethodNode =
          new utils.XPath(referenceNode.getXml(), 'DigestMethod');
      digestValueNode = new utils.XPath(referenceNode.getXml(), 'DigestValue');

      transformsNode = new utils.XPath(referenceNode.getXml(), 'Transforms');
    } catch (error) {
      throw new exceptions.CryptoLibException(
          'Could not parse signed XML file', error);
    }

    // there are signatures without key, so we ignore them
    var rsaKeyValueNode;
    var modulusNode;
    var exponentNode;
    try {
      rsaKeyValueNode = new utils.XPath(
          signatureNode.getXml(), 'KeyInfo/KeyValue/RSAKeyValue');
      modulusNode = new utils.XPath(rsaKeyValueNode.getXml(), 'Modulus');
      exponentNode = new utils.XPath(rsaKeyValueNode.getXml(), 'Exponent');
    } catch (error) {
      // ignore if this part fails
    }

    // build the object structure
    /** @property {object} */
    this.info = {
      method: signatureMethodNode.getAttribute('Algorithm'),
      value: signatureValueNode.getValue(),
      meta: signatureNode.getAttribute('xmlns')
    };
    /** @property {object} */
    this.canonicalization = {
      method: canonicalizationMethodNode.getAttribute('Algorithm')
    };
    /** @property {object} */
    this.reference = {
      uri: referenceNode.getAttribute('URI'),
      transforms: [],
      digest: {
        method: digestMethodNode.getAttribute('Algorithm'),
        value: digestValueNode.getValue()
      }
    };
    /** @property {object} */
    this.rsakey = {
      modulus: modulusNode ? modulusNode.getValue() : '',
      exponent: exponentNode ? exponentNode.getValue() : ''
    };

    // update the transforms references
    var transforms = transformsNode.getChildren();
    for (var i = 0; i < transforms.length; i++) {
      if (transforms[i].nodeType === Node.ELEMENT_NODE) {
        var transformNode = new utils.XPath(transforms[i]);
        this.reference.transforms.push(transformNode.getAttribute('Algorithm'));
      }
    }
  }

  /**
   * @class
   * @memberof asymmetric/xmlsigner
   */
  function ExclusiveCanonicalization() {}

  /**
   * Sorts the attributes
   *
   * @function
   * @param xmlNode
   *            xml node, as a DOM.
   */
  ExclusiveCanonicalization.prototype.sortAttributes = function(xmlNode) {
    // gather all attributes and remove them
    var list = [];
    var attr;
    if (xmlNode.attributes) {
      for (var i = 0; i < xmlNode.attributes.length; i++) {
        attr = xmlNode.attributes[i];
        list.push({id: attr.name, val: xmlNode.getAttribute(attr.name)});
        xmlNode.removeAttribute(attr.name);
        i--;
      }
    }

    // sort the attributes
    list.sort(function(a, b) {
      if (a.id < b.id) {
        return -1;
      }
      if (a.id > b.id) {
        return 1;
      }
      return 0;
    });

    // reinsert the attributes
    for (var j = 0; j < list.length; j++) {
      attr = list[j];
      xmlNode.setAttribute(attr.id, attr.val);
    }
  };

  /**
   * Inits the process of canonicalization.
   *
   * @function
   * @param xmlNode
   *            xml node, as a DOM.
   *
   * @return xml node processed, as DOM.
   */
  ExclusiveCanonicalization.prototype.process = function(xml) {
    // traverse the tree through all the children
    for (var i = 0; i < xml.childNodes.length; i++) {
      var child = xml.childNodes[i];

      if (child.nodeType === Node.ELEMENT_NODE) {
        this.process(child);
      }
      // if are comments or other stuff, remove them
      else if (child.nodeType !== Node.TEXT_NODE) {
        child.parentNode.removeChild(child);
        --i;
      }
    }

    // sort the attributes
    this.sortAttributes(xml);

    // return the final object
    return xml;
  };

  /**
   * An xml signer.
   *
   * @class
   * @memberof asymmetric/xmlsigner
   */
  function CryptoForgeXmlSigner() {}

  CryptoForgeXmlSigner.prototype = {

    /**
     * Verifies the digital signature of a selfsigned xml.
     *
     * @function
     * @param publicKey
     *            Public key, as an object.
     * @param signedXml
     *            XML data, as a string
     * @param signatureParentNode
     *            The node where the signature is, as a string.
     *
     * @returns Boolean indicating whether signature verification was
     *          successful.
     */
    verify: function(publicKey, signedXml, signatureParentNode) {

      // clean the comments in the xml
      signedXml = this._removeXmlStringComments(signedXml);

      // loads the text to an xml
      var xml = parsers.xmlFromString(signedXml);

      // loads the xml signature structure to json
      var signature = this._loadSignature(xml, signatureParentNode);

      // check all the algorithms are the expected ones
      this._verifyAlgorithmsInSignature(signature);

      // check the public key is the expected one
      this._verifyPublicKeyInSignature(signature, publicKey);

      // check the references are well encoded
      this._validateReferences(signature, signedXml);

      // validates the signatures are right
      this._validateSignature(signature, publicKey);

      // if not exception raised, all went good
      return true;
    },

    /**
     * Gets the XML signature node from the main XML node
     *
     * @function
     * @private
     * @param xml
     *            XML where the signature is, as a Document Object
     * @param signatureParentNode
     *            The node where the signature is, as a string.
     *
     * @returns Object with the DOM node of the signature and and the
     *          signature in json.
     */
    _loadSignature: function(xml, signatureParentNode) {
      // access to the node where the signature is, and take the methods
      try {
        // get the signature node
        var xmlNode = new utils.XPath(xml, signatureParentNode);

        // create signature object to load the data from the signature
        var data = new XmlSignature(xmlNode);

        // return the main object
        return {xmlNode: xmlNode, data: data};
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'Could not load the signature from the XML', error);
      }
    },

    /**
     * Verifies that the method algorithms in the EML and the properties
     * match.
     *
     * @function
     * @private
     * @param signature
     *            Signature, as a Javascript Object
     */
    _verifyAlgorithmsInSignature: function(signature) {
      try {
        if (signature.data.canonicalization.method !==
            'http://www.w3.org/2001/10/xml-exc-c14n#') {
          throw new exceptions.CryptoLibException(
              'Invalid canonicalization algorithm');
        }

        if (!stringUtils.containsSubString(
                signature.data.info.method, policies.algorithm) &&
            !stringUtils.containsSubString(
                signature.data.info.method, policies.hash)) {
          throw new exceptions.CryptoLibException(
              'Invalid signature algorithm');
        }

        if (!stringUtils.containsSubString(
                signature.data.reference.digest.method, policies.hash)) {
          throw new exceptions.CryptoLibException('Invalid digest algorithm');
        }
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'Could not verify the data in the XML.', error);
      }
    },

    /**
     * Verifies the public key is the same than the one in the EML.
     *
     * @function
     * @private
     * @param signature
     *            Signature, as a Javascript Object
     * @param publicKey
     *            Public key, as an object.
     */
    _verifyPublicKeyInSignature: function(signature, publicKey) {
      // it has no key
      if (!signature.data.rsakey.exponent || !signature.data.rsakey.modulus) {
        return;
      }

      // if it has key, we validate it
      try {
        var signatureExponent =
            converters.base64ToBigInteger(signature.data.rsakey.exponent);
        var signatureModulus =
            converters.base64ToBigInteger(signature.data.rsakey.modulus);

        if (publicKey.e.compareTo(signatureExponent) !== 0 ||
            publicKey.n.compareTo(signatureModulus) !== 0) {
          throw new exceptions.CryptoLibException('Invalid public key');
        }
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'Could not verify the data in the XML.', error);
      }
    },

    /**
     * Verifies the references of a selfsigned xml.
     *
     * @function
     * @private
     * @param signature
     *            Signature, as a Javascript Object
     */
    _validateReferences: function(signature, xmlString) {
      try {
        var xmlCanonString;

        // The canonicalization transformations are performed here.
        // Due to differences in the behaviour of Internet Explorer,
        // these transformations must be applied manually in the case
        // of that browser. For all other browsers, the transformations
        // are performed by applying a canonicalization algorithm.

        if (parsers.isIE()) {
          xmlCanonString = this._removeXmlStringHeader(xmlString);

          xmlCanonString = this._removeXmlStringSelfClosingTags(xmlCanonString);

          xmlCanonString = this._removeXmlStringSignature(xmlCanonString);

          xmlCanonString = this._removeXmlStringFirstCharacter(xmlCanonString);
        } else {
          var xmlCanon = null;

          // apply all transforms to the xml
          for (var i = 0; i < signature.data.reference.transforms.length; i++) {
            // get the algorithm name
            var algorithm = signature.data.reference.transforms[i];

            // consume it if it exists
            if (algorithm in this._transformAlgorithmMap) {
              xmlCanon = this._transformAlgorithmMap[algorithm](
                  signature.xmlNode.getXml());
            }
          }

          xmlCanon = this._removeXmlHeader(xmlCanon);
          xmlCanonString = parsers.xmlToString(xmlCanon, true);
        }

        var digester =
            this._getHashMethod(signature.data.reference.digest.method);
        digester.start();

        digester.update(
            parsers.removeCarriageReturnChars(xmlCanonString), 'utf8');
        var messageDigest = digester.digest();

        // check if the result is valid
        var encodedMessage = forge.util.encode64(messageDigest.getBytes());

        if (encodedMessage !== signature.data.reference.digest.value) {
          throw new exceptions.CryptoLibException(
              'Invalid reference: \'' + encodedMessage +
              '\' when was expected \'' +
              signature.data.reference.digest.value + '\'');
        }
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'Could not validate references.', error);
      }
    },

    /**
     * Verifies the digital signature of a selfsigned xml.
     *
     * @function
     * @private
     * @param signature
     *            Signature, as a Javascript Object
     * @param publicKey
     *            Public key, as an object.
     */
    _validateSignature: function(signature, publicKey) {
      try {
        var signedInfoNode =
            new utils.XPath(signature.xmlNode.getXml(), '/SignedInfo');
        var algorithm = signature.data.canonicalization.method;

        var xmlCanon = null;
        if (algorithm in this._transformAlgorithmMap) {
          xmlCanon =
              this._transformAlgorithmMap[algorithm](signedInfoNode.getXml());
        }

        var digester = this._getHashMethod(signature.data.info.method);
        digester.start();
        digester.update(parsers.xmlToString(xmlCanon, true), 'utf8');

        var signatureValue = forge.util.decode64(signature.data.info.value);

        var result =
            publicKey.verify(digester.digest().getBytes(), signatureValue);

        if (!result) {
          throw new exceptions.CryptoLibException(
              'Could not validate the signature.');
        }
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'Could not validate signatures.', error);
      }
    },

    /**
     * Creates the hash method
     *
     * @function
     * @private
     * @param method
     *            method type, as string.
     *
     * @return Object, the disgester of that method.
     */
    _getHashMethod: function(method) {
      if (stringUtils.containsSubString(method, 'sha256')) {
        return forge.md.sha256.create();
      }
    },

    /**
     * Map with all tarnsform algorithms
     *
     * @function
     * @private
     */
    _transformAlgorithmMap: {
      /**
       * Removes the signature from a signed XML
       *
       * @param xmlSignature
       *            the xml signature, as DOM.
       *
       * @return the XML top document, as DOM
       */
      'http://www.w3.org/2000/09/xmldsig#enveloped-signature': function(
          xmlSignature) {

        var topElement = xmlSignature;

        // get the top document
        while (topElement.parentNode) {
          topElement = topElement.parentNode;
        }

        // remove the signature from the node
        xmlSignature.parentNode.removeChild(xmlSignature);

        return topElement;
      },

      /**
       * c14n the XML
       *
       * @param xmlNode
       *            the xml node, as DOM.
       *
       * @return the XML top document, as DOM
       */
      'http://www.w3.org/2001/10/xml-exc-c14n#': function(xmlNode) {

        var transform = new ExclusiveCanonicalization();
        return transform.process(xmlNode);
      }
    },

    /**
     * Removes the comments in the xml
     *
     * @function
     * @private
     * @param xml
     *            the xml, as string.
     *
     * @return {string} the xml without comments
     */
    _removeXmlStringComments: function(xml) {
      return xml.replace(/<!--[\s\S]*?-->/g, '');
    },

    /**
     * Removes the header in the xml structure. <?xml version="1.0"
     * encoding="UTF-8"?>
     *
     * @function
     * @private
     * @param {object}
     *            xmlDocument the xml, as DOM.
     *
     * @returns {object} the xml, as DOM without the header
     */
    _removeXmlHeader: function(xmlDocument) {
      return xmlDocument.documentElement ? xmlDocument.documentElement :
                                           xmlDocument;
    },

    /**
     * Removes the header in the XML string.
     *
     * @param xmlString
     *            the xml, as a string.
     *
     * @return the xml string without the header
     */
    _removeXmlStringHeader: function(xmlString) {
      return parsers.removeXmlHeaderFromString(xmlString);
    },

    /**
     * Removes the signature node in the XML string.
     *
     * @param xmlString
     *            the xml, as a string.
     *
     * @return the xml string without the signature tag
     */
    _removeXmlStringSignature: function(xmlString) {
      return parsers.removeXmlSignature(xmlString);
    },

    /**
     * Converts any self-closing tags to opening and closing tags, within an
     * XML string.
     *
     * @param xmlString
     *            the xml, as a string.
     *
     * @return the xml string with self-closing tags converted to opening
     *         and closing tags
     */
    _removeXmlStringSelfClosingTags: function(xmlString) {
      return parsers.xmlRemoveSelfClosingTags(xmlString);
    },

    /**
     * Removes the first character from an XML string if that character is
     * not the expected initial character.
     *
     * @param xmlString
     *            the xml, as a string.
     *
     * @return the xml string with the initial character removed if it is
     *         not wanted
     */
    _removeXmlStringFirstCharacter: function(xmlString) {

      if (xmlString[0] !== '<') {
        return xmlString.slice(1, xmlString.length);
      } else {
        return xmlString;
      }
    }
  };
};

/*
 * 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
 */
/** @namespace certificates */
cryptolib.modules.certificates = function(box) {
  'use strict';

  if (box.certificates) {
    return;
  }

  box.certificates = {};

  /**
   * Provides an API that can be used to perform operations with certificates.
   *
   * @exports certificates/service
   */
  box.certificates.service = {};

  var converters;
  var exceptions;

  cryptolib('commons', function(box) {
    converters = new box.commons.utils.Converters();
    exceptions = box.commons.exceptions;
  });

  /**
   * Loads a certificate in pem format.
   *
   * @function
   * @param {string}
   *            certificatePem certificate as string in PEM format.
   * @returns {certificates.box.certificates.CryptoX509Certificate} the
   *          certificate.
   */
  box.certificates.service.load = function(certificatePem) {
    return new box.certificates.CryptoX509Certificate(certificatePem);
  };

  /**
   * Provides methods to access X509 certificate data.
   *
   * @class
   * @param certificatePem
   *            certificate as string in PEM format.
   * @memberof certificates
   */
  box.certificates.CryptoX509Certificate = function(certificatePem) {
    /** @property {object} certificate the certificate. * */
    this.certificate = forge.pki.certificateFromPem(certificatePem, true);
  };

  box.certificates.CryptoX509Certificate.prototype = {
    /**
     * Retrieves the public key of the certificate.
     *
     * @function
     * @returns public key, as string in PEM format.
     * @memberof certificates.box.certificates.CryptoX509Certificate
     */
    getPublicKey: function() {
      var publicKey = this.certificate.publicKey;
      if (publicKey !== null) {
        return forge.pki.publicKeyToPem(publicKey);
      } else {
        throw new exceptions.CryptoLibException(
            'Could not find public key in certificate.');
      }
    },

    /**
     * Retrieves the start time of the certificate's validity.
     *
     * @function
     * @returns start time of certificate validity, as Date object.
     * @memberof certificates.box.certificates.CryptoX509Certificate
     */
    getNotBefore: function() {
      var notBeforeField = this.certificate.validity.notBefore;
      if (notBeforeField !== null) {
        return notBeforeField;
      } else {
        throw new exceptions.CryptoLibException(
            'Could not find validity start time in certificate.');
      }
    },

    /**
     * Retrieves the end time of the certificate's validity.
     *
     * @function
     * @returns end time of certificate validity, as Date object.
     * @memberof certificates.box.certificates.CryptoX509Certificate
     */
    getNotAfter: function() {
      var notAfterField = this.certificate.validity.notAfter;
      if (notAfterField !== null) {
        return notAfterField;
      } else {
        throw new exceptions.CryptoLibException(
            'Could not find validity end time in certificate.');
      }
    },

    /**
     * Retrieves the serial number of the certificate.
     *
     * @function
     * @returns serial number of certificate, as hexadecimal string.
     * @memberof certificates.box.certificates.CryptoX509Certificate
     */
    getSerialNumber: function() {
      var serialNumber = this.certificate.serialNumber;
      if (serialNumber !== null) {
        return serialNumber;
      } else {
        return '';
      }
    },

    /**
     * Retrieves the issuer common name of the certificate.
     *
     * @function
     * @returns issuer common name of certificate, as string.
     * @memberof certificates.box.certificates.CryptoX509Certificate
     */
    getIssuerCN: function() {
      var issuerCNField =
          this.certificate.issuer.getField({name: 'commonName'});
      if (issuerCNField !== null) {
        return issuerCNField.value;
      } else {
        return '';
      }
    },

    /**
     * Retrieves the issuer organizational unit of the certificate.
     *
     * @function
     * @returns issuer organizational unit of certificate, as string.
     * @memberof certificates.box.certificates.CryptoX509Certificate
     */
    getIssuerOrgUnit: function() {
      var orgUnitField = this.certificate.issuer.getField({shortName: 'OU'});
      if (orgUnitField !== null) {
        return orgUnitField.value;
      } else {
        return '';
      }
    },

    /**
     * Retrieves the issuer organization name of the certificate.
     *
     * @function
     * @returns Issuer organization name of certificate, as string.
     * @memberof certificates.box.certificates.CryptoX509Certificate
     */
    getIssuerOrg: function() {
      var orgNameField =
          this.certificate.issuer.getField({name: 'organizationName'});
      if (orgNameField !== null) {
        return orgNameField.value;
      } else {
        return '';
      }
    },

    /**
     * Retrieves the issuer locality name of the certificate.
     *
     * @function
     * @returns Locality name of certificate, as string.
     * @memberof certificates.box.certificates.CryptoX509Certificate
     */
    getIssuerLocality: function() {
      var localityField =
          this.certificate.issuer.getField({name: 'localityName'});
      if (localityField !== null) {
        return localityField.value;
      } else {
        return '';
      }
    },

    /**
     * Retrieves the issuer country name of the certificate.
     *
     * @function
     * @returns Issuer country name of certificate, as string.
     * @memberof certificates.box.certificates.CryptoX509Certificate
     */
    getIssuerCountry: function() {
      var countryField =
          this.certificate.issuer.getField({name: 'countryName'});
      if (countryField !== null) {
        return countryField.value;
      } else {
        return '';
      }
    },

    /**
     * Retrieves the subject common name of the certificate.
     *
     * @function
     * @returns Subject common name of certificate, as string.
     * @memberof certificates.box.certificates.CryptoX509Certificate
     */
    getSubjectCN: function() {
      var subjectCNField =
          this.certificate.subject.getField({name: 'commonName'});
      if (subjectCNField !== null) {
        return subjectCNField.value;
      } else {
        return '';
      }
    },

    /**
     * Retrieves the subject organizational unit of the certificate.
     *
     * @function
     * @returns Subject organizational unit of certificate, as string.
     * @memberof certificates.box.certificates.CryptoX509Certificate
     */
    getSubjectOrgUnit: function() {
      var orgUnitField = this.certificate.subject.getField({shortName: 'OU'});
      if (orgUnitField !== null) {
        return orgUnitField.value;
      } else {
        return '';
      }
    },

    /**
     * Retrieves the subject organization name of the certificate.
     *
     * @function
     * @return Subject organization name of certificate, as string.
     * @memberof certificates.box.certificates.CryptoX509Certificate
     */
    getSubjectOrg: function() {
      var organizationNameField =
          this.certificate.subject.getField({name: 'organizationName'});
      if (organizationNameField !== null) {
        return organizationNameField.value;
      } else {
        return '';
      }
    },

    /**
     * Retrieves the subject locality name of the certificate.
     *
     * @function
     * @returns Subject locality name of certificate, as string.
     * @memberof certificates.box.certificates.CryptoX509Certificate
     */
    getSubjectLocality: function() {
      var localityField =
          this.certificate.subject.getField({name: 'localityName'});
      if (localityField !== null) {
        return localityField.value;
      } else {
        return '';
      }
    },

    /**
     * Retrieves the subject country name of the certificate.
     *
     * @function
     * @returns Subject country name of certificate, as string.
     * @memberof certificates.box.certificates.CryptoX509Certificate
     */
    getSubjectCountry: function() {
      var countryField =
          this.certificate.subject.getField({name: 'countryName'});
      if (countryField !== null) {
        return countryField.value;
      } else {
        return '';
      }
    },

    /**
     * Retrieves the key usage extension of the certificate.
     *
     * @function
     * @return KeyUsageExtension key usage extension, as KeyUsageExtension
     *         object.
     * @memberof certificates.box.certificates.CryptoX509Certificate
     */
    getKeyUsageExtension: function() {
      var keyUsageExt = this.certificate.getExtension({name: 'keyUsage'});

      if (keyUsageExt !== null) {
        var keyUsageMap = {};
        keyUsageMap.digitalSignature = keyUsageExt.digitalSignature;
        keyUsageMap.nonRepudiation = keyUsageExt.nonRepudiation;
        keyUsageMap.keyEncipherment = keyUsageExt.keyEncipherment;
        keyUsageMap.dataEncipherment = keyUsageExt.dataEncipherment;
        keyUsageMap.keyAgreement = keyUsageExt.keyAgreement;
        keyUsageMap.keyCertSign = keyUsageExt.keyCertSign;
        keyUsageMap.crlSign = keyUsageExt.cRLSign;
        keyUsageMap.encipherOnly = keyUsageExt.encipherOnly;
        keyUsageMap.decipherOnly = keyUsageExt.decipherOnly;

        return new box.certificates.KeyUsageExtension(keyUsageMap);
      } else {
        return null;
      }
    },

    /**
     * Retrieves the basic constraints of the certificate.
     *
     * @function
     * @returns map containing the basic constraints.
     * @memberof certificates.box.certificates.CryptoX509Certificate
     */
    getBasicConstraints: function() {
      var basicConstraints =
          this.certificate.getExtension({name: 'basicConstraints'});

      if (basicConstraints !== null) {
        var basicConstraintsMap = {};

        basicConstraintsMap.ca = basicConstraints.cA;

        return basicConstraintsMap;
      } else {
        return null;
      }
    },

    /**
     * Retrieves the digital signature of the certificate.
     *
     * @function
     * @returns digital signature of certificate, as string in Base64
     *          encoded format.
     * @memberof certificates.box.certificates.CryptoX509Certificate
     */
    getSignature: function() {
      var signature = this.certificate.signature;
      if (signature !== null) {
        var signatureB64 =
            converters.base64Encode(signature, box.RSA_LINE_LENGTH);
        if (signatureB64 !== null) {
          return signatureB64;
        } else {
          throw new exceptions.CryptoLibException(
              'Base64 encoding of signature is null.');
        }
      } else {
        throw new exceptions.CryptoLibException('Signature is null.');
      }
    },

    /**
     * Verifies the digital signature of the certificate provided as input,
     * using this certificate's public key.
     *
     * @function
     * @param certificatePem
     *            certificate whose signature is to be verified, as string
     *            in PEM format.
     * @returns boolean indicating whether signature was verified.
     * @memberof certificates.box.certificates.CryptoX509Certificate
     */
    verify: function(certificatePem) {
      var certificate = forge.pki.certificateFromPem(certificatePem);

      if (certificate !== null) {
        return this.certificate.verify(certificate);
      } else {
        throw new exceptions.CryptoLibException('Certificate is null.');
      }
    },

    /**
     * Retrieves the certificate, in PEM format.
     *
     * @function
     * @returns certificate, as string in PEM format
     * @memberof certificates.box.certificates.CryptoX509Certificate
     */
    toPem: function() {
      if (this.certificate !== null) {
        return forge.pki.certificateToPem(
            this.certificate, box.RSA_LINE_LENGTH);
      } else {
        throw new exceptions.CryptoLibException('Certificate is null.');
      }
    }

  };

  /**
   * Container class for the key usage extension flags of a digital
   * certificate.
   *
   * @class
   * @param keyUsageMap
   *            map containing name-value pairs of key usage extension flags.
   * @memberof certificates
   */
  box.certificates.KeyUsageExtension = function(keyUsageMap) {
    this.keyUsageMap = keyUsageMap;
  };

  box.certificates.KeyUsageExtension.prototype = {

    /**
     * Retrieves the digital signature flag of the key usage extension.
     *
     * @function
     * @returns boolean indicating whether digital signature flag is set.
     * @memberof certificates.box.certificates.KeyUsageExtension
     */
    digitalSignature: function() {
      return this.keyUsageMap.digitalSignature;
    },

    /**
     * Retrieves the non-repudiation flag of the key usage extension.
     *
     * @function
     * @return boolean indicating whether non-repudiation flag is set.
     * @memberof certificates.box.certificates.KeyUsageExtension
     */
    nonRepudiation: function() {
      return this.keyUsageMap.nonRepudiation;
    },

    /**
     * Retrieves the key encipherment flag of the key usage extension.
     *
     * @function
     * @returns boolean indicating whether key encipherment flag is set.
     * @memberof certificates.box.certificates.KeyUsageExtension
     */
    keyEncipherment: function() {
      return this.keyUsageMap.keyEncipherment;
    },

    /**
     * Retrieves the data encipherment flag of the key usage extension.
     *
     * @function
     * @returns boolean indicating whether data encipherment flag is set.
     * @memberof certificates.box.certificates.KeyUsageExtension
     */
    dataEncipherment: function() {
      return this.keyUsageMap.dataEncipherment;
    },

    /**
     * Retrieves the key agreement flag of the key usage extension.
     *
     * @function
     * @returns boolean indicating whether key agreement flag is set.
     * @memberof certificates.box.certificates.KeyUsageExtension
     */
    keyAgreement: function() {
      return this.keyUsageMap.keyAgreement;
    },

    /**
     * Retrieves the key certificate sign flag of the key usage extension.
     *
     * @function
     * @returns Boolean indicating whether key certificate sign flag is set.
     * @memberof certificates.box.certificates.KeyUsageExtension
     */
    keyCertSign: function() {
      return this.keyUsageMap.keyCertSign;
    },

    /**
     * Retrieves the CRL sign flag of the key usage extension.
     *
     * @function
     * @returns boolean indicating whether CRL sign flag is set.
     * @memberof certificates.box.certificates.KeyUsageExtension
     */
    crlSign: function() {
      return this.keyUsageMap.crlSign;
    },

    /**
     * Retrieves the encipher only flag of the key usage extension.
     *
     * @function
     * @returns boolean indicating whether encipher only flag is set.
     * @memberof certificates.box.certificates.KeyUsageExtension
     */
    encipherOnly: function() {
      return this.keyUsageMap.encipherOnly;
    },

    /**
     * Retrieves the decipher only flag of the key usage extension.
     *
     * @function
     * @returns boolean indicating whether decipher only flag is set.
     * @memberof certificates.box.certificates.KeyUsageExtension
     */
    decipherOnly: function() {
      return this.keyUsageMap.decipherOnly;
    },

    /**
     * Retrieves the CA flag of the key usage extension.
     *
     * @function
     * @return boolean indicating whether CA flag is set.
     * @memberof certificates.box.certificates.KeyUsageExtension
     */
    ca: function() {
      return this.keyUsageMap.ca;
    }
  };

  /**
   * Class that validates the content of a CryptoX509Certificate.
   *
   * @class
   * @param validationData
   * @param {string}
   *            validationData.subject
   * @param {string}
   *            validationData.subject.commonName
   * @param {string}
   *            validationData.subject.organization
   * @param {string}
   *            validationData.subject.organizationUnit
   * @param {string}
   *            validationData.subject.country
   * @param {string}
   *            validationData.issuer
   * @param {string}
   *            validationData.issuer.commonName
   * @param {string}
   *            validationData.issuer.organization
   * @param {string}
   *            validationData.issuer.organizationUnit
   * @param {string}
   *            validationData.issuer.country
   * @param {string}
   *            validationData.time
   * @param {string}
   *            validationData.keytype CA, Sign or Encryption
   * @param {string}
   *            validationData.caCertificatePem
   * @memberof certificates
   */
  box.certificates.CryptoX509CertificateValidator = function(validationData) {
    var parseIsoDate = function(isoDate) {
      var dateChunks = isoDate.split(/\D/);
      return new Date(Date.UTC(
          +dateChunks[0], --dateChunks[1], +dateChunks[2], +dateChunks[5],
          +dateChunks[6], +dateChunks[7], 0));
    };

    /**
     * Validates the content of the X.509 certificate.
     *
     * @function
     * @param certificatePem
     *            certificate as string in PEM format.
     * @return an array containing the errors type, if any.
     */
    this.validate = function(x509CertificatePem) {
      var cryptoX509Certificate =
          new box.certificates.CryptoX509Certificate(x509CertificatePem);
      var failedValidations = [];

      var validateSubject = function(subject) {
        if (subject.commonName !== cryptoX509Certificate.getSubjectCN() ||
            subject.organization !== cryptoX509Certificate.getSubjectOrg() ||
            subject.organizationUnit !==
                cryptoX509Certificate.getSubjectOrgUnit() ||
            subject.country !== cryptoX509Certificate.getSubjectCountry()) {
          failedValidations.push('SUBJECT');
        }
      };

      var validateIssuer = function(issuer) {
        if (issuer.commonName !== cryptoX509Certificate.getIssuerCN() ||
            issuer.organization !== cryptoX509Certificate.getIssuerOrg() ||
            issuer.organizationUnit !==
                cryptoX509Certificate.getIssuerOrgUnit() ||
            issuer.country !== cryptoX509Certificate.getIssuerCountry()) {
          failedValidations.push('ISSUER');
        }
      };

      var validateTime = function(isoDate) {

        var time = parseIsoDate(isoDate);

        if (time.toString() === 'Invalid Date' ||
            time - cryptoX509Certificate.getNotBefore() < 0 ||
            time - cryptoX509Certificate.getNotAfter() > 0) {
          failedValidations.push('TIME');
        }
      };

      var areBasicConstraintsValid = function(keyType) {
        var returnValue = true;
        var basicConstraints = cryptoX509Certificate.getBasicConstraints();

        if (keyType === 'CA' &&
            (!basicConstraints || basicConstraints.ca !== true)) {
          returnValue = false;
        }

        return returnValue;
      };

      var isKeyUsageValid = function(keyType) {
        var returnValue = true;

        var keyUsageExtension = cryptoX509Certificate.getKeyUsageExtension();
        if (!keyUsageExtension) {
          return false;
        }

        switch (keyType) {
          case 'CA':
            if (keyUsageExtension.keyCertSign() !== true ||
                keyUsageExtension.crlSign() !== true) {
              returnValue = false;
            }
            break;
          case 'Sign':
            if (keyUsageExtension.digitalSignature() !== true ||
                keyUsageExtension.nonRepudiation() !== true) {
              returnValue = false;
            }
            break;
          case 'Encryption':
            if (keyUsageExtension.keyEncipherment() !== true ||
                keyUsageExtension.dataEncipherment() !== true) {
              returnValue = false;
            }
            break;
          default:
            returnValue = false;
        }

        return returnValue;
      };

      var validateKeyType = function(keyType) {
        if (!areBasicConstraintsValid(keyType) || !isKeyUsageValid(keyType)) {
          failedValidations.push('KEY_TYPE');
        }
      };

      var validateSignature = function(caCertificatePem) {

        var caCryptoX509Certificate =
            new box.certificates.CryptoX509Certificate(caCertificatePem);
        if (caCryptoX509Certificate === null) {
          failedValidations.push('SIGNATURE');
          throw new exceptions.CryptoLibException('CA certificate is null.');
        }

        try {
          if (!caCryptoX509Certificate.verify(x509CertificatePem)) {
            failedValidations.push('SIGNATURE');
          }
        } catch (error) {
          failedValidations.push('SIGNATURE');
          throw new exceptions.CryptoLibException(
              'Signature verification process failed.');
        }
      };

      if (validationData.subject) {
        validateSubject(validationData.subject);
      }

      if (validationData.issuer) {
        validateIssuer(validationData.issuer);
      }

      if (validationData.time) {
        validateTime(validationData.time);
      }

      if (validationData.keyType) {
        validateKeyType(validationData.keyType);
      }

      if (validationData.caCertificatePem) {
        validateSignature(validationData.caCertificatePem);
      }

      return failedValidations;
    };
  };

  /**
   * Class that validates the content of a CryptoX509Certificate.
   *
   * @function
   * @param validationData
   *            {json}
   * @param {string}
   *            validationData.subject the subject data.
   * @param {string}
   *            validationData.subject.commonName
   * @param {string}
   *            validationData.subject.organization
   * @param {string}
   *            validationData.subject.organizationUnit
   * @param {string}
   *            validationData.subject.country
   * @param {string}
   *            validationData.issuer the issuer data.
   * @param {string}
   *            validationData.issuer.commonName
   * @param {string}
   *            validationData.issuer.organization
   * @param {string}
   *            validationData.issuer.organizationUnit
   * @param {string}
   *            validationData.issuer.country
   * @param {string}
   *            validationData.time time to be checked.
   * @param {string}
   *            validationData.keyType CA, Sign or Encryption.
   * @param {string}
   *            validationData.caCertificatePem
   * @param certificate
   *            certificate as string in PEM format.
   * @returns {Array} an array that contains the error types occurred, if any.
   */
  box.certificates.service.validateCertificate = function(
      validationData, certificate) {

    if (validationData === null) {
      throw new exceptions.CryptoLibException('Validation data is null.');
    }

    if (Object.getOwnPropertyNames(validationData).length) {
      throw new exceptions.CryptoLibException('Validation data is empty.');
    }

    if (certificate === null) {
      throw new exceptions.CryptoLibException('Certificate is null.');
    }

    return (new box.certificates.CryptoX509CertificateValidator(validationData))
        .validate(certificate);
  };

  /**
   * Validates the certificate information provided to the constructor. The
   * validation process loops through all certificates, starting with the leaf
   * certificate, until it reaches the trusted certificate. For each
   * certificate, except the trusted certificate, it checks that the following
   * conditions hold:
   * <ul>
   * <li>Subject DN is that expected for given certificate.</li>
   * <li>Issuer DN is same as subject DN of next certificate in chain.</li>
   * <li>Key type is that expected: "signing" or "encryption" for leaf
   * certificate and "CA" for rest of certificates in chain.</li>
   * <li>Signature can be verified with public key of next certificate in
   * chain.</li>
   * <li>Starting time is earlier than ending time.</li>
   * <li>Starting time is equal to or later than starting time of next
   * certificate in chain.</li>
   * <li>Ending time is equal to or earlier than ending time of next
   * certificate in chain.</li>
   * </ul>
   * In addition, if a non-null value is provided to the constructor for the
   * time reference, it will be checked whether this time reference is within
   * the dates of validity of the leaf certificate. After the validation
   * process has completed, a list of strings will be returned. If this list
   * is empty, then the validation was successful. Otherwise, the list will
   * contain string identifiers for each type of validation that failed.
   *
   * @function
   * @param {json}
   *            chain certificate chain.
   * @param {json}
   *            chain.leaf leaf certificate.
   * @param {string}
   *            chain.leaf.pem X.509 certificate.
   * @param {string}
   *            chain.leaf.keyType keyType.
   * @param {string}
   *            chain.leaf.subject subject.
   * @param {string}
   *            chain.leaf.time time reference (Optional). Only mandatory if
   *            the 'Time' validation is going to be performed.
   * @param {json}
   *            chain.certificates chain of certificates.
   * @param {Array}
   *            chain.certificates.pems list of X.509 Certificates as string
   *            in pem format. The list starts by the one closer to the root.
   * @param {Array}
   *            chain.certificates.subjects list of subjects
   * @param {Array}
   *            chain.root X.509 trusted Certificate.
   * @returns {Array} a two dimension array where the first dimension is the
   *          index of the element starting by 1 -leaf certificate - and the
   *          second dimension holds the error types.
   */
  box.certificates.service.validateX509CertificateChain = function(chain) {
    var validateDateRange = function(certificate, failedValidation) {
      if (certificate.getNotBefore() > certificate.getNotAfter()) {
        failedValidation.push('VALIDITY_PERIOD');
      }
    };

    var validateNotBefore = function(
        notBefore, previousNotBefore, failedValidation) {
      if (notBefore < previousNotBefore) {
        failedValidation.push('NOT_BEFORE');
      }
    };

    var validateNotAfter = function(
        notAfter, previousNotAfter, failedValidation) {
      if (notAfter > previousNotAfter) {
        failedValidation.push('NOT_AFTER');
      }
    };

    var validateLeafCertificate = function(
        issuer, signature, previousNotBefore, previousNotAfter) {
      var validationData = {
        subject: chain.leaf.subject,
        issuer: issuer,
        keyType: chain.leaf.keyType,
        caCertificatePem: signature
      };

      if (chain.leaf.time) {
        validationData.time = chain.leaf.time;
      }

      var certificateValidator =
          new box.certificates.CryptoX509CertificateValidator(validationData);
      var failedValidations = certificateValidator.validate(chain.leaf.pem);

      var certificate =
          new box.certificates.CryptoX509Certificate(chain.leaf.pem);
      validateDateRange(certificate, failedValidations);
      validateNotBefore(
          certificate.getNotBefore(), previousNotBefore, failedValidations);
      validateNotAfter(
          certificate.getNotAfter(), previousNotAfter, failedValidations);

      return failedValidations;
    };

    var rootCertificate =
        new box.certificates.CryptoX509Certificate(chain.root);

    var issuer = {
      commonName: rootCertificate.getSubjectCN(),
      organization: rootCertificate.getSubjectOrg(),
      organizationUnit: rootCertificate.getSubjectOrgUnit(),
      country: rootCertificate.getSubjectCountry()
    };
    var signature = chain.root;
    var failedValidation = [];
    var previousNotBefore = rootCertificate.getNotBefore();
    var previousNotAfter = rootCertificate.getNotAfter();

    var certificateValidator;
    chain.certificates.pems.reverse();
    chain.certificates.subjects.reverse();
    for (var i = 0; i < chain.certificates.pems.length; i++) {
      var certificate = new box.certificates.CryptoX509Certificate(
          chain.certificates.pems[i]);

      var validationData = {
        subject: chain.certificates.subjects[i],
        issuer: issuer,
        keyType: 'CA',
        signature: signature
      };

      certificateValidator =
          new box.certificates.CryptoX509CertificateValidator(validationData);
      failedValidation[i] =
          certificateValidator.validate(chain.certificates.pems[i]);

      validateDateRange(certificate, failedValidation[i]);
      validateNotBefore(
          certificate.getNotBefore(), previousNotBefore, failedValidation[i]);
      validateNotAfter(
          certificate.getNotAfter(), previousNotAfter, failedValidation[i]);

      issuer = {
        commonName: certificate.getSubjectCN(),
        organization: certificate.getSubjectOrg(),
        organizationUnit: certificate.getSubjectOrgUnit(),
        country: certificate.getSubjectCountry()
      };
      signature = chain.certificates.pems[i];
      previousNotBefore = certificate.getNotBefore();
      previousNotAfter = certificate.getNotAfter();
    }

    failedValidation.push(validateLeafCertificate(
        issuer, signature, previousNotBefore, previousNotAfter));

    failedValidation.reverse();

    return failedValidation;
  };

  /**
   * Flattens failed validations to a one dimensional array to a one
   * dimensional array.
   *
   * @function
   * @param {array}
   *            failedValidations a two-dimensional failed validations array.
   * @returns {array} a flat array in which each element contains the error
   *          type message piped with the index of the element, i.e.
   * ERROR_<element index>.
   */
  box.certificates.service.flattenFailedValidations = function(
      failedValidations) {
    var flattenFailedValidations = Array();
    for (var i = 0; i < failedValidations.length; i++) {
      if (failedValidations[i] !== undefined) {
        for (var j = 0; j < failedValidations[i].length; j++) {
          flattenFailedValidations.push(
              failedValidations[i][j].toLowerCase() + '_' + (i));
        }
      }
    }

    return flattenFailedValidations;
  };
};

cryptolib.modules.certificates.service = cryptolib.modules.certificates;

/*
 * 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
 */
cryptolib.modules.commons = cryptolib.modules.commons || {};

/**
 * Defines our exception wrapper.
 *
 * @param message.
 *            The string to use in the error description.
 */
cryptolib.modules.commons.exceptions = function(box) {
  'use strict';

  box.commons = box.commons || {};
  box.commons.exceptions = {};

  box.commons.exceptions.CryptoLibException = function(
      customMessage, originalMessage) {
    this.customMessage = customMessage;
    this.originalMessage = originalMessage;
  };

  box.commons.exceptions.CryptoLibException.prototype = {
    /**
     * Returns the exception message.
     *
     * @return {string} The message.
     */
    toString: function() {

      var errorMessage = 'ERROR:  ';

      if (typeof this.originalMessage !== 'undefined') {
        errorMessage += this.customMessage +
            '. Original Error Message: ' + this.originalMessage;
      } else {
        errorMessage += this.customMessage;
      }
      return errorMessage;
    }
  };
};

/*
 * 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
 */
cryptolib.modules.commons = cryptolib.modules.commons || {};

/**
 * @namespace commons/mathematical
 */
cryptolib.modules.commons.mathematical = function(box) {
  'use strict';

  box.commons = box.commons || {};

  /**
   * A module the defines mathematical groups, elements, and operations that
   * can be performed
   *
   * @exports commons/mathematical
   */
  box.commons.mathematical = {};

  var BigInteger = box.forge.jsbn.BigInteger;

  var exceptions;
  var converters;
  cryptolib('commons.exceptions', 'commons.utils', function(box) {
    exceptions = box.commons.exceptions;
    converters = new box.commons.utils.Converters();
  });

  /**
   * Given p and Zp representing 'Integers (mod p)' group, a Zp subgroup is a
   * group where the elements are a subset of elements from Zp.
   *
   * The order of the subgroup is q, that is the number of elements. Both Zp
   * and the ZpSubgroup are finite cyclic groups, it means that all elements
   * can be generated exponentiating a special group element called generator.
   *
   * When the p and q are related with the restriction p = 2q + 1 the subgroup
   * is also defined as 'Quadratic Residue'.
   *
   * @class
   * @param valueG
   *            {forge.jsbn.BigInteger} The generator of the subgroup, as a
   *            BigInteger. This value must be a group element different from
   *            one.
   * @param valueP
   *            {forge.jsbn.BigInteger} The modulus, as a BigInteger.
   * @param valueQ
   *            {forge.jsbn.BigInteger} The order of the subgroup, as a
   *            BigInteger.
   *
   * @throws CryptoLibException
   *             if 0 < q < p restriction is not accomplished
   * @throws CryptoLibException
   *             if subgroup generator is nor between 2 and p-1
   *
   */
  box.commons.mathematical.ZpSubgroup = function(valueG, valueP, valueQ) {

    if ((typeof valueG === 'undefined') || (typeof valueP === 'undefined') ||
        (typeof valueQ === 'undefined')) {
      throw new exceptions.CryptoLibException(
          'The given parameters should be initialized');
    }

    if ((BigInteger.ONE.compareTo(valueG) >= 0) ||
        (valueP.compareTo(valueG) <= 0)) {
      throw new exceptions.CryptoLibException(
          'The generator should be between 2 and p-1');
    }

    if ((BigInteger.ZERO.compareTo(valueQ) >= 0) ||
        (valueP.compareTo(valueQ) <= 0)) {
      throw new exceptions.CryptoLibException(
          'The relationship between the values of the p and q parameters should be such that 0 < q < p');
    }

    var _p = valueP;
    var _q = valueQ;
    var generator = new box.commons.mathematical.ZpGroupElement(valueG, _p, _q);
    var identity =
        new box.commons.mathematical.ZpGroupElement(BigInteger.ONE, _p, _q);

    /**
     * @function
     * @returns the identity element of the group.
     */
    this.getIdentity = function() {
      return identity;
    };

    /**
     * @function
     * @returns the q parameter (the order of the group).
     */
    this.getQ = function() {
      return _q;
    };

    /**
     * @function
     * @returns the p parameter (the modulus of the group).
     */
    this.getP = function() {
      return _p;
    };

    /**
     * @function
     * @returns {box.commons.mathematical.ZpGroupElement} the generator
     *          element of the group.
     */
    this.getGenerator = function() {
      return generator;
    };
  };

  box.commons.mathematical.ZpSubgroup.prototype = {

    /**
     * Checks whether a given element is a member of this MathematicalGroup.
     *
     * An element is a member of the group if:
     * <ul>
     * <li><code>element</code> has an integer value between
     * <code>1</code> and <code>p-1</code>:
     * <code>(0 < element < p)</code></li>
     * <li><code>element<sup>q</sup> mod p = 1</code></li>
     * </ul>
     *
     * @function
     * @param element
     *            {ZpGroupElement} the element whose group membership should
     *            be checked.
     * @returns true if the given element is member of the group, otherwise
     *          false.
     */
    isGroupMember: function(element) {
      if (typeof element !== 'undefined' &&
          element.getP().equals(this.getP())) {
        var modPow = element.getElementValue().modPow(this.getQ(), this.getP());
        return BigInteger.ONE.equals(modPow);
      } else {
        return false;
      }
    },

    /**
     * Check if this group is equal to the received group.
     *
     * @function
     * @param group
     *            {ZpSubgroup} the group which should be checked against
     *            this group for equality.
     * @returns true if the given group has the same <code>q</code>, the
     *          same <code>p</code>, and the same <code>generator</code>.
     */
    equals: function(group) {
      if (typeof group !== 'undefined' &&
          group.getGenerator().equals(this.getGenerator()) &&
          group.getQ().equals(this.getQ()) &&
          group.getP().equals(this.getP())) {
        return true;
      } else {
        return false;
      }
    },

    /**
     * Check if this group is a quadratic residue group, which is defined
     * such that p = 2q + 1.
     *
     * @function
     * @returns true if the given group is a quadratic residue group.
     */
    isQuadraticResidueGroup: function() {
      return this.getP().equals(
          new BigInteger('2').multiply(this.getQ()).add(BigInteger.ONE));
    },

    /**
     * Displays a string representation of this mathematical group JSON.
     * <p>
     * Note: in order to permit interoperability between libraries, this
     * representation should match the equivalent representation in Java.
     *
     * @function
     * @returns a string representation of this mathematical group JSON.
     */
    stringify: function() {
      return JSON.stringify({
        zpSubgroup: {
          p: converters.base64FromBigInteger(this.getP()),
          q: converters.base64FromBigInteger(this.getQ()),
          g: converters.base64FromBigInteger(
              this.getGenerator().getElementValue())
        }
      });
    }
  };

  /**
   * Class representing elements of a Zp group.
   *
   * <p>
   * Note: This constructor does not check whether the given value is a valid
   * member of the subgroup. To check subgroup membership the function
   * <code>zpSubgroup.isGroupMember(groupElement)</code> can be called,
   * however this is a computationally expensive operation.
   *
   * @class
   * @param value
   *            {forge.jsbn.BigInteger} Value of the element (not null). The
   *            value must be between <code>[1..p-1]</code>
   * @param groupP
   *            {forge.jsbn.BigInteger} The p parameter, as a BigInteger.
   * @param groupQ
   *            {forge.jsbn.BigInteger} The q parameter, as a BigInteger.
   * @throws CryptoLibException
   *             if there are any problems with the inputs.
   */
  box.commons.mathematical.ZpGroupElement = function(value, groupP, groupQ) {

    if ((typeof value === 'undefined')) {
      throw new exceptions.CryptoLibException(
          'The received value is not the expected object');
    }

    if ((BigInteger.ZERO.compareTo(value) >= 0) ||
        (groupP.compareTo(value) <= 0)) {
      throw new exceptions.CryptoLibException(
          'The value of the element should be between 1 and p-1. P: ' + groupP +
          ', value: ' + value);
    }

    if ((BigInteger.ZERO.compareTo(groupQ) >= 0) ||
        (groupP.compareTo(groupQ) <= 0)) {
      throw new exceptions.CryptoLibException(
          'The relationship between the values of the p and q parameters should hold that 0 < q < p');
    }

    var v = value;
    var p = groupP;
    var q = groupQ;

    /**
     * Performs a basic check to verify if this element belongs to the same
     * group as <code>element</code> by checking if the p and q parameters
     * of both are equal.
     * <p>
     * Note: this function does NOT perform the mathematical operation
     * needed to actually confirm that a value is an element of a group,
     * which involves modular exponentiation and is very computationally
     * costly. Instead, this function simply examines the p and q parameters
     * that are stored within both elements.
     *
     * @function
     * @param element
     *            {ZpGroupElement} element to by multiplied with this
     *            element.
     * @throws CryptoLibException
     *             if the p and q parameters of the group that the received
     *             element belongs to are not equal to the p and q
     *             parameters of this element.
     */
    this._validateReceivedElementIsFromSameGroup = function(element) {
      if (typeof element === 'undefined') {
        throw new exceptions.CryptoLibException(
            'The received value is not the expected object');
      }
      if ((!this.getP().equals(element.getP())) ||
          (!this.getQ().equals(element.getQ()))) {
        throw new exceptions.CryptoLibException(
            'Operations can only be performed on group elements which are members of the same Zp subgroup');
      }
    };

    /**
     * Performs a basic check to verify if this element belongs to the same
     * group as <code>exponent</code> by checking if the p and q
     * parameters of both are equal.
     * <p>
     * Note: this function does NOT perform the mathematical operation
     * needed to actually confirm that an exponent belongs to a particular
     * group, which would involve confirming the following:
     * <p>
     * <code>0 >= exponent value <= p-1</code>
     *
     * @function
     * @param exponent
     *            {Exponent} element to by multiplied with this element.
     * @throws CryptoLibException
     *             if p and q parameters of the group that the received
     *             element belongs to are not equal to the p and q
     *             parameters of this element.
     */
    this._validateReceivedExponent = function(exponent) {
      if (typeof exponent === 'undefined') {
        throw new exceptions.CryptoLibException(
            'The received value is not the expected object');
      }

      if (!this.getQ().equals(exponent.getQ())) {
        throw new exceptions.CryptoLibException(
            'The exponent should have the same q as this Zp group element');
      }
    };

    /**
     * @function
     * @returns The value of this element.
     */
    this.getElementValue = function() {
      return v;
    };

    /**
     * @function
     * @returns The p parameter of the mathematical group to which this
     *          element belongs.
     */
    this.getP = function() {
      return p;
    };

    /**
     * @function
     * @returns The q parameter of the mathematical group to which this
     *          element belongs.
     */
    this.getQ = function() {
      return q;
    };
  };

  box.commons.mathematical.ZpGroupElement.prototype = {

    /**
     * Multiple this element by the received element. The operation is
     * performed mod p. Performs a basic check to confirm that this element
     * and the received element belong to the same group.
     *
     * @function
     * @param element
     *            {ZpGroupElement} element to by multiplied with this
     *            element.
     * @returns <code>(this * element) mod p</code>, as a ZpGroupElement.
     * @throws CryptoLibException
     *             if the p and q parameters of the group that the received
     *             element belongs to are not equal to the p and q
     *             parameters of this element.
     */
    multiply: function(element) {
      this._validateReceivedElementIsFromSameGroup(element);

      var result = this.getElementValue()
                       .multiply(element.getElementValue())
                       .mod(this.getP());
      return new box.commons.mathematical.ZpGroupElement(
          result, this.getP(), this.getQ());
    },

    /**
     * Exponentiate this element to the received exponent.
     *
     * @function
     * @param exponent
     *            {Exponent} the exponent used to raise the value of the
     *            group element.
     * @returns <code>(this<sup>exponent</sup>) mod p</code>, as a
     *          ZpGroupElement.
     * @throws CryptoLibException
     *             if the p and q parameters of the group that the received
     *             exponent belongs to are not equal to the p and q
     *             parameters of this element.
     */
    exponentiate: function(exponent) {
      this._validateReceivedExponent(exponent);

      var result =
          this.getElementValue().modPow(exponent.getValue(), this.getP());
      return new box.commons.mathematical.ZpGroupElement(
          result, this.getP(), this.getQ());
    },

    /**
     * Get the inverse of this element, mod p.
     *
     * @function
     * @returns the inverse of this element mod p, as a ZpGroupElement.
     */
    invert: function() {
      var result = this.getElementValue().modInverse(this.getP());
      return new box.commons.mathematical.ZpGroupElement(
          result, this.getP(), this.getQ());
    },

    /**
     * Check if this element is equal to the received element.
     * <p>
     * Elements are considered equal if:
     * <ul>
     * <li>They have the same value.</li>
     * <li>They belong to the same group (which is confirmed here by
     * checking if their p and q parameters are equal).</li>
     * </ul>
     *
     * @function
     * @param element
     *            {ZpGroupElement} the element which should be checked
     *            against this element for equality.
     * @returns true if the given group element has the same <code>v</code>,
     *          the same <code>p</code> and the same <code>q</code>.
     */
    equals: function(element) {
      if (typeof element !== 'undefined' &&
          element.getElementValue().equals(this.getElementValue()) &&
          element.getP().equals(this.getP()) &&
          element.getQ().equals(this.getQ())) {
        return true;
      } else {
        return false;
      }
    },

    /**
     * Displays a string representation of this Zp group element JSON.
     * <p>
     * Note: in order to permit interoperability between libraries, this
     * representation should match the equivalent representation in Java.
     *
     * @function
     * @returns a string representation of Zp group element JSON.
     */
    stringify: function() {
      return JSON.stringify({
        zpGroupElement: {
          p: converters.base64FromBigInteger(this.getP()),
          q: converters.base64FromBigInteger(this.getQ()),
          value: converters.base64FromBigInteger(this.getElementValue())
        }
      });
    }
  };

  /**
   * Represents an exponent for a particular mathematical group.
   * <p>
   * The value of an exponent must be within the range [0..q-1]. If the
   * received value is not within this range, then the value that will be
   * assigned to the created Exponent will be calculated as follows:
   * <p>
   * <code>value = value mod q</code>
   *
   * @class
   *
   * @param q
   *            {forge.jsbn.BigInteger} the order of the mathematical group
   *            for which this is an exponent.
   * @param exponentValue
   *            {forge.jsbn.BigInteger} the value of the exponent.
   * @throws CryptoLibException
   *             if there are any problems with the inputs.
   */
  box.commons.mathematical.Exponent = function(valueQ, exponentValue) {

    if (valueQ === 'undefined' || valueQ.compareTo(BigInteger.ZERO) === 0) {
      throw new exceptions.CryptoLibException(
          'Q (the order of the group) cannot be null or zero');
    }
    var _q = valueQ;

    /**
     * Gets the value to set for this Exponent. This value has to be a
     * number between <code>0</code> and <code>q-1</code> (inclusive),
     * so if it is less than <code>0</code> or greater than
     * <code>q-1</code>, then <code>mod q</code> has to be applied.
     *
     * @function
     * @param {forge.jsbn.BigInteger}
     *            exponentValue the value of the exponent.
     * @returns the value to set to this exponent.
     */
    function getExponent(exponentValue) {
      if ((_q.compareTo(exponentValue) > 0) &&
          (BigInteger.ZERO.compareTo(exponentValue) <= 0)) {
        return exponentValue;
      } else {
        return exponentValue.mod(_q);
      }
    }

    var _value = getExponent(exponentValue);

    /**
     * Checks if this exponent and the received exponent belong to the same
     * group.
     *
     * @function
     * @param exponent
     *            {Exponent} the exponent which should be checked to see if
     *            it belongs to the same group as this exponent.
     * @throws CryptoLibException
     *             if the received exponent is undefined or does not belong
     *             to the same group as this exponent.
     */
    this._confirmSameGroup = function(exponent) {
      if (typeof exponent === 'undefined') {
        throw new exceptions.CryptoLibException(
            'The received exponent is not the expected object');
      }

      if (!_q.equals(exponent.getQ())) {
        throw new exceptions.CryptoLibException(
            'Operations may only be performed with exponents of the same mathematical group order');
      }
    };

    /**
     * @function
     * @returns The numeric value of the exponent.
     */
    this.getValue = function() {
      return _value;
    };

    /**
     * @function
     * @returns The numeric value of the exponent.
     */
    this.getQ = function() {
      return _q;
    };
  };

  box.commons.mathematical.Exponent.prototype = {

    /**
     * Returns an Exponent whose value is:
     * <p>
     * <code>(this + exponent) mod q</code>
     *
     * @param exponent
     *            {Exponent} the exponent to be added to this exponent.
     * @returns <code>(this + exponent) mod q</code>
     */
    add: function(exponent) {
      this._confirmSameGroup(exponent);
      var result = this.getValue().add(exponent.getValue()).mod(this.getQ());
      return new box.commons.mathematical.Exponent(this.getQ(), result);
    },

    /**
     * Returns an Exponent whose value is:
     * <p>
     * <code>(this - exponent) mod q</code>
     *
     * @param exponent
     *            {Exponent} the exponent to be subtracted from this
     *            exponent.
     * @returns <code>(this - exponent) mod q</code>
     */
    subtract: function(exponent) {
      this._confirmSameGroup(exponent);
      var result =
          this.getValue().subtract(exponent.getValue()).mod(this.getQ());
      return new box.commons.mathematical.Exponent(this.getQ(), result);
    },

    /**
     * Returns an Exponent whose value is:
     * <p>
     * <code>(this * exponent) mod q</code>
     *
     * @param exponent
     *            {Exponent} the exponent to be multiplied with this
     *            exponent.
     * @returns <code>(this * exponent) mod q</code>
     */
    multiply: function(exponent) {
      this._confirmSameGroup(exponent);
      var result =
          this.getValue().multiply(exponent.getValue()).mod(this.getQ());
      return new box.commons.mathematical.Exponent(this.getQ(), result);
    },

    /**
     * Returns an Exponent whose value is <code>(-this) mod q</code>
     *
     * @returns <code>(-this mod q)</code>
     */
    negate: function() {
      return new box.commons.mathematical.Exponent(
          this.getQ(), this.getValue().negate().mod(this.getQ()));
    },

    /**
     * Check if this exponent is equal to the received exponent.
     * <p>
     * Elements are considered equal if:
     * <ul>
     * <li>They have the same value.</li>
     * <li>They belong to the groups with the same order.</li>
     * </ul>
     *
     * @param {Exponent}
     *            exponent the exponent to be checked against this exponent
     *            for equality.
     * @returns true if the given exponent has the same <code>value</code>
     *          and belongs to the same <code>group</code> as this
     *          exponent.
     */
    equals: function(exponent) {
      if (exponent.getValue().equals(this.getValue()) &&
          exponent.getQ().equals(this.getQ())) {
        return true;
      } else {
        return false;
      }
    },

    /**
     * Displays a string representation of the exponent JSON.
     * <p>
     * Note: in order to permit interoperability between libraries, this
     * representation should match the equivalent representation in Java.
     *
     * @function
     * @returns a string representation of the exponent JSON.
     */
    stringify: function() {
      return JSON.stringify({
        exponent: {
          q: converters.base64FromBigInteger(this.getQ()),
          value: converters.base64FromBigInteger(this.getValue())
        }
      });
    }
  };

  /**
   * Provides utility functionality for working with mathematical groups.
   *
   * @class groupUtils
   * @memberof commons/mathematical
   */
  box.commons.mathematical.groupUtils = (function() {

    /**
     * Deserializes a Zp Subgroup string representation to a ZpSubgroup
     * object.
     *
     * @function
     * @memberof commons/mathematical.groupUtils
     * @param groupJson
     *            {JSON} a JSON representation of a Zp subgroup.
     * @returns a new ZpSubgroup, created from the received string.
     */
    function deserializeGroup(groupJson) {
      var parsed = JSON.parse(groupJson);

      var p = converters.base64ToBigInteger(parsed.zpSubgroup.p);
      var q = converters.base64ToBigInteger(parsed.zpSubgroup.q);
      var g = converters.base64ToBigInteger(parsed.zpSubgroup.g);

      return new box.commons.mathematical.ZpSubgroup(g, p, q);
    }

    /**
     * Deserializes a Zp Subgroup element string to a ZpGroupElement object.
     *
     * @function
     * @memberof commons/mathematical.groupUtils
     * @param groupElementJson
     *            {JSON} a JSON representation of a Zp Subgroup element
     *            element.
     * @returns a new ZpGroupElement, created from the received string.
     */
    function deserializeGroupElement(groupElementJson) {
      var parsed = JSON.parse(groupElementJson);

      var value = converters.base64ToBigInteger(parsed.zpGroupElement.value);
      var p = converters.base64ToBigInteger(parsed.zpGroupElement.p);
      var q = converters.base64ToBigInteger(parsed.zpGroupElement.q);

      return new box.commons.mathematical.ZpGroupElement(value, p, q);
    }

    /**
     * Deserializes an exponent string to an Exponent object.
     *
     * @function
     * @memberof commons/mathematical.groupUtils
     * @param exponentJson
     *            {JSON} a JSON representation of an exponent.
     * @returns a new Exponent, created from the received string.
     */
    function deserializeExponent(exponentJson) {
      var parsed = JSON.parse(exponentJson);

      var q = converters.base64ToBigInteger(parsed.exponent.q);
      var value = converters.base64ToBigInteger(parsed.exponent.value);

      return new box.commons.mathematical.Exponent(q, value);
    }

    /**
     * Compress a list of group elements.
     * <P>
     * Note: an exception will be thrown if an array is received which
     * contains any elements that are not elements of the mathematical
     * group.
     *
     * @function
     * @memberof commons/mathematical.groupUtils
     * @param group
     *            {ZpSubgroup} the mathematical group.
     * @param elementsToBeCompressedArray
     *            {object} the array of group elements to be compressed.
     * @returns the result of the compression process, as a group element.
     * @throws CryptoLibException
     *             if there are any problems with the inputs.
     */
    function compressGroupElements(group, elementsToBeCompressedArray) {
      validateArray(elementsToBeCompressedArray);
      validateGroup(group);

      var elem = elementsToBeCompressedArray[0];

      for (var i = 1; i < elementsToBeCompressedArray.length; i++) {
        elem = elem.multiply(elementsToBeCompressedArray[i]);
      }

      return elem;
    }

    /**
     * Compress a list of exponents.
     * <P>
     * Note: an exception will be thrown if an array is received which
     * contains any exponents that are not exponents of the mathematical
     * group.
     *
     * @function
     * @memberof commons/mathematical.groupUtils
     * @param group
     *            {ZpSubgroup} the mathematical group
     * @param exponentsToBeCompressedArray
     *            {object} the array of exponents to be compressed
     * @returns the result of the compression process, as an exponent.
     * @throws CryptoLibException
     *             if there are any problems with the inputs.
     */
    function compressExponents(group, exponentsToBeCompressedArray) {
      validateArray(exponentsToBeCompressedArray);
      validateGroup(group);
      validateExponents(group.getQ(), exponentsToBeCompressedArray);

      var elem = exponentsToBeCompressedArray[0];

      for (var i = 1; i < exponentsToBeCompressedArray.length; i++) {
        elem = elem.add(exponentsToBeCompressedArray[i]);
      }

      return elem;
    }

    /**
     * Builds a new array of elements from the input array, where the final
     * elements from the input array are compressed into a simple element.
     *
     * @function
     * @memberof commons/mathematical.groupUtils
     * @param group
     *            {ZpSubgroup} the mathematical group that this compressor
     *            operates on.
     * @param inputArray
     *            {object} the input array of group elements.
     * @param numElementsRequiredInNewList
     *            {number} the number of elements that should be in the
     *            output array.
     * @returns a new array of group elements.
     * @throws CryptoLibException
     *             if there are any problems with the inputs.
     */
    function buildListWithCompressedFinalElement(
        group, inputArray, numElementsRequiredInNewList) {
      validateArray(inputArray);
      validateGroup(group);

      if (numElementsRequiredInNewList < 1) {
        throw new exceptions.CryptoLibException(
            'The number of elements in the output list must be at least 1');
      }

      var offset = numElementsRequiredInNewList - 1;
      var outputArray = inputArray.slice(0, offset);

      var lastElement = compressGroupElements(
          group, inputArray.slice(offset, inputArray.length));

      outputArray.push(lastElement);

      return outputArray;
    }

    /**
     * Builds a new array of exponents from the input array, where the final
     * exponents from the input array are compressed into a simple
     * exponents.
     *
     * @function
     * @memberof commons/mathematical.groupUtils
     * @param group
     *            {ZpSubgroup} the mathematical group that this compressor
     *            operates on.
     * @param inputArray
     *            {object} the input array of exponents.
     * @param numExponentsRequiredInNewList
     *            {number} the number of exponents that should be in the
     *            output array.
     * @returns a new array of group elements.
     * @throws CryptoLibException
     *             if there are any problems with the inputs.
     */
    function buildListWithCompressedFinalExponent(
        group, inputArray, numExponentsRequiredInNewList) {
      validateArray(inputArray);
      validateGroup(group);

      if (numExponentsRequiredInNewList < 1) {
        throw new exceptions.CryptoLibException(
            'The number of exponents in the output list must be at least 1');
      }

      var offset = numExponentsRequiredInNewList - 1;
      var outputArray = inputArray.slice(0, offset);

      var lastExponent =
          compressExponents(group, inputArray.slice(offset, inputArray.length));

      outputArray.push(lastExponent);

      return outputArray;
    }

    /**
     * Given two arrays of group elements, this method produces a new list
     * of group elements that represents the two input lists combined.
     * <P>
     * The two arrays must only contain values that are elements of the
     * mathematical group that this divider operates on. If either array
     * contains any element that is not a group element then an exception
     * will be thrown.
     *
     * @function
     * @memberof commons/mathematical.groupUtils
     * @param arrayElements1
     *            {object} an input array of Zp group elements.
     * @param arrayElements2
     *            {object} an input array of Zp group elements.
     * @returns a new list of group elements.
     * @throws CryptoLibException
     *             if there are any problems with the inputs.
     */
    function divide(arrayElements1, arrayElements2) {
      validateArray(arrayElements1);
      validateArray(arrayElements2);

      if (arrayElements1.length !== arrayElements2.length) {
        throw new exceptions.CryptoLibException(
            'Both arrays should have the same length');
      }

      var newList = [];
      var newElement;

      for (var i = 0; i < arrayElements1.length; i++) {
        newElement = arrayElements1[i].multiply(arrayElements2[i].invert());

        newList.push(newElement);
      }

      return newList;
    }

    /**
     * Given an array of group elements and one exponent, this method
     * produces a new list of group elements that represents the
     * exponentiation of the group elements to the exponent.
     * <P>
     * The array must contain values that are elements of the mathematical
     * group that this exponentiation activity operates on. If the array
     * contains any element that is not a group element then an exception
     * will be thrown.
     *
     * @function
     * @memberof commons/mathematical.groupUtils
     * @param {array} arrayElements
     *             an input array of Zp group elements.
     * @param {object} exponent
     *            An Exponent.
     * @param {object} group
     *            The mathematical group that all of the elements in
     *            arrayElements belong to.
     * @param {boolean} validateElements An Exponent.
     * @return A new list of group elements.
     *
     */
    function exponentiateArrays(
        arrayElements, exponent, group, validateElements) {
      validateArray(arrayElements);
      validateExponent(exponent);
      validateGroup(group);

      // If requested, validate all elements prior to any operations.
      if (typeof validateElements !== 'undefined' && validateElements) {
        for (var j = 0; j < arrayElements.length; j++) {
          if (!group.isGroupMember(arrayElements[j])) {
            throw new exceptions.CryptoLibException(
                'The element does not belong to the group');
          }
        }
      }

      var newList = [];
      var newElement;

      for (var i = 0; i < arrayElements.length; i++) {
        newElement = arrayElements[i].exponentiate(exponent);

        newList.push(newElement);
      }

      return newList;
    }

    /**
     * Returns a new random exponent for the received mathematical group.
     * <P>
     * The value of the created exponent will be between <code>0</code>
     * and q-1.
     *
     * @function
     * @memberof commons/mathematical.groupUtils
     * @param group
     *            {ZpSubgroup} the mathematical group.
     * @param cryptoRandomInteger
     *            {CryptoScytlRandomInteger} the source of randomness.
     * @param {boolean}
     *            useShortExponent true if a short exponent is to be
     *            generated. Default value is false.
     * @returns a new random exponent.
     * @throws CryptoLibException
     *             if there are any problems with the inputs.
     */
    function generateRandomExponent(
        group, cryptoRandomInteger, useShortExponent) {
      validateGroup(group);

      if (typeof cryptoRandomInteger === 'undefined') {
        throw new exceptions.CryptoLibException(
            'The given parameters should be initialized');
      }

      var _useShortExponent = true;
      if (!useShortExponent) {
        _useShortExponent = false;
      }

      var q = group.getQ();
      var qBitLength = q.bitLength();
      var randomExponentLength;
      if (_useShortExponent) {
        if (qBitLength < box.SHORT_EXPONENT_BIT_LENGTH) {
          throw new exceptions.CryptoLibException(
              'Zp subgroup order bit length must be greater than or equal to short exponent bit length : ' +
              box.SHORT_EXPONENT_BIT_LENGTH + '; Found ' + qBitLength);
        }
        randomExponentLength = box.SHORT_EXPONENT_BIT_LENGTH;
      } else {
        randomExponentLength = qBitLength;
      }

      var randomExponentValue;
      var randomExponentFound = false;
      while (!randomExponentFound) {
        randomExponentValue =
            cryptoRandomInteger.nextRandomByBits(randomExponentLength);
        if (randomExponentValue.compareTo(q) < 0) {
          randomExponentFound = true;
        }
      }

      return new box.commons.mathematical.Exponent(q, randomExponentValue);
    }

    /**
     * Returns a group element symmetric key which is obtained by
     * exponentiating the generator of a given mathematical group to a
     * random exponent <code>k</code>.
     *
     * @function
     * @memberof commons/mathematical.groupUtils
     * @param group
     *            {ZpSubgroup} the mathematical group.
     * @param cryptoRandomInteger
     *            {CryptoScytlRandomInteger} the source of randomness.
     * @returns the secret key, as a group element.
     * @throws CryptoLibException
     *             if there are any problems with the inputs.
     */
    function generateGroupElementSecretKey(group, cryptoRandomInteger) {
      var exponent = generateRandomExponent(group, cryptoRandomInteger);

      var groupElementSecretKey = group.getGenerator().exponentiate(exponent);

      return groupElementSecretKey;
    }

    /**
     * Build a ZpSubgroup from the p and g parameters.
     * <P>
     * NOTE: This method builds a particular type of mathematical group with
     * property that:
     * <P>
     * P = (Q * 2) + 1
     * <P>
     * This property holds for all groups generated using this method, but
     * this property does not hold for all mathematical groups.
     *
     * @function
     * @memberof commons/mathematical.groupUtils
     * @param p
     *            {forge.jsbn.BigInteger} the p parameter of the group.
     * @param g
     *            {forge.jsbn.BigInteger} the generator of the group.
     * @returns the generated ZpSubgroup.
     * @throws CryptoLibException
     *             if there are any problems with the inputs.
     */
    function buildZpSubgroupFromPAndG(p, g) {
      validateBuildFromPAndGInputs(p, g);

      var q = p.subtract(BigInteger.ONE).divide(new BigInteger('2'));
      return new box.commons.mathematical.ZpSubgroup(g, p, q);
    }

    /**
     * Build a random ZpSubgroup whose p parameter will have the specified
     * bit length. The certainty that both p and q are both prime is
     * specified by certainty.
     * <P>
     * NOTE: This method builds a particular type of Zp subgroup with
     * property that:
     * <P>
     * p = (q * 2) + 1
     * <P>
     * This property holds for all mathematical groups generated using this
     * method, but this property does not hold for all mathematical groups.
     * <P>
     * Note: the minimum bit length of the p parameter that is permitted by
     * this method is 2. If a bit length less than 2 is requested then a
     * CryptoLibException will be thrown.
     *
     * @function
     * @memberof commons/mathematical.groupUtils
     * @param bitLengthOfP
     *            {number} the bit length that the p parameter of the
     *            generated group should be.
     * @param cryptoRandomInteger
     *            {CryptoScytlRandomInteger} an instance of
     *            CryptoRandomInteger that can be used as a source of random
     *            integers.
     * @param certainty
     *            {number} the required level of certainty that the q
     *            parameter of the generated group is prime.
     * @returns the generated ZpSubgroup.
     * @throws CryptoLibException
     *             if there are any problems with the inputs.
     */
    function buildRandomZpSubgroupFromLengthOfP(
        bitLengthOfP, cryptoRandomBytes, certainty) {
      validateBuildFromLengthPInputs(
          bitLengthOfP, cryptoRandomBytes, certainty);

      var p, q;
      var twoBI = new BigInteger('2');
      var randomGroupFound = false;

      var options = {prng: cryptoRandomBytes};

      var callback = function(err, num) {
        if (err) {
          throw new exceptions.CryptoLibException(
              'Error while generating prime ' + err);
        }
        p = num;
        q = num.subtract(BigInteger.ONE).divide(twoBI);
      };

      while (!randomGroupFound) {
        forge.prime.generateProbablePrime(bitLengthOfP, options, callback);

        if (q.isProbablePrime(certainty)) {
          randomGroupFound = true;
        }
      }

      var generator = findSmallestGenerator(p, q);

      return new box.commons.mathematical.ZpSubgroup(generator, p, q);
    }

    /**
     * Given an input array containing possible group members, this method
     * cycles through that array, checking if each element is a group
     * member. This method attempts to find the specified number of group
     * members. Once that number has been found, the method will return a
     * new array containing the specified number of group members.
     * <p>
     * If the required number of group members are not found in the input
     * array then a CryptoLibException will be thrown.
     *
     * @function
     * @memberof commons/mathematical.groupUtils
     * @param possibleGroupMembersArray
     *            {object} an array containing possible group members.
     * @param group
     *            {ZpSubgroup} the Zp subgroup for which the specified
     *            number of group members are required.
     * @param numMembersRequired
     *            {number} the number of group members of the specified
     *            group that should be added to the output list.
     * @returns a new array containing the specified number of group
     *          elements, which have been read from the input array.
     * @throws CryptoLibException
     *             if there are any problems with the inputs.
     */
    function extractNumberOfGroupMembersFromListOfPossibleMembers(
        possibleGroupMembersArray, group, numMembersRequired) {
      validateExtractFromListInputs(
          possibleGroupMembersArray, group, numMembersRequired);

      var outputArray = [];
      var membersFound = 0;
      var candidate;

      for (var i = 0; i < possibleGroupMembersArray.length; i++) {
        candidate = new box.commons.mathematical.ZpGroupElement(
            possibleGroupMembersArray[i], group.getP(), group.getQ());

        if (group.isGroupMember(candidate)) {
          outputArray.push(candidate);
          membersFound++;
        }

        if (membersFound === numMembersRequired) {
          return outputArray;
        }
      }

      if (membersFound !== numMembersRequired) {
        throw new exceptions.CryptoLibException(
            'Error - did not find the required number of group members in the given list. The required number of was ' +
            numMembersRequired + ', number of members found was  ' +
            membersFound);
      }
    }

    /**
     * Find the smallest generator of a mathematical group.
     * <p>
     * Note: starts with a candidate generator value (two is the initial
     * candidate generator value), and checks if the candidate generator is
     * a group memeber. If that candidate is a group memeber then return
     * that value, else increment the candidate by one and again check if
     * the candidate is a group member. Continue this process until the
     * candidate value is less than the parameter p.
     *
     * @function
     * @memberof commons/mathematical.groupUtils
     * @params p {number} the p parameter of the group.
     * @params q {number} the q parameter of the group.
     * @throws CryptoLibException
     */
    function findSmallestGenerator(p, q) {
      var g = new BigInteger('2');
      var generatorFound = false;

      while ((!generatorFound) && (g.compareTo(p) < 0)) {
        if (g.modPow(q, p).equals(BigInteger.ONE)) {
          generatorFound = true;
        } else {
          g = g.add(BigInteger.ONE);
        }
      }

      if (!generatorFound) {
        throw new exceptions.CryptoLibException(
            'Failed to find a generator, p was: ' + p + ', q was: ' + q);
      }

      return g;
    }

    /**
     * Validates a group.
     *
     * @function
     * @memberof commons/mathematical.groupUtils
     * @params group {ZpSubgroup} the group to be validated.
     * @throws CryptoLibException
     *             if the received group is undefined.
     */
    function validateGroup(group) {
      if (typeof group === 'undefined' || null === group) {
        throw new exceptions.CryptoLibException(
            'The given group should be initialized');
      }
    }

    function validateExponent(exponent) {
      if (typeof exponent === 'undefined') {
        throw new exceptions.CryptoLibException(
            'The given exponent should be initialized');
      }
    }

    /**
     * Validates that an array is initialized and non-empty.
     *
     * @function
     * @memberof commons/mathematical.groupUtils
     * @params inputArray {object} an array to be validated.
     * @throws CryptoLibException
     *             if the received array is not initialized or is empty.
     */
    function validateArray(inputArray) {
      if (typeof inputArray === 'undefined') {
        throw new exceptions.CryptoLibException(
            'The given array should be initialized');
      }

      if (!(inputArray instanceof Array)) {
        throw new exceptions.CryptoLibException(
            'The given array are not from the expected type');
      }

      if (inputArray.length < 1) {
        throw new exceptions.CryptoLibException(
            'The given array cannot be empty');
      }
    }

    /**
     * Performs a very basic validation on the received inputs.
     *
     * @function
     * @memberof commons/mathematical.groupUtils
     * @params p {object} the first parameter.
     * @params g {object} the second parameter.
     * @throws CryptoLibException
     *             if either (or both) inputs are undefined.
     */
    function validateBuildFromPAndGInputs(p, g) {
      if ((typeof p === 'undefined') || (typeof g === 'undefined')) {
        throw new exceptions.CryptoLibException('p or g are incorrect');
      }
    }

    /**
     * Performs a some basic validations on the received inputs.
     *
     * @function
     * @memberof commons/mathematical.groupUtils
     * @params bitLengthOfP {number} bit length of p parameter.
     * @params cryptoRandomBytes {CryptoScytlRandomBytes} a secure source of
     *         random bytes.
     * @params certainty {number} a certainty level.
     * @throws CryptoLibException
     *             if any of the inputs fail any of the validations.
     */
    function validateBuildFromLengthPInputs(
        bitLengthOfP, cryptoRandomBytes, certainty) {
      if (bitLengthOfP < 2) {
        throw new exceptions.CryptoLibException(
            'The bit length should be higher');
      }

      if (typeof cryptoRandomBytes === 'undefined') {
        throw new exceptions.CryptoLibException(
            'The random generator should be initialized');
      }

      if (certainty < box.MINIMUM_PRIME_CERTAINTY_LEVEL) {
        throw new exceptions.CryptoLibException('Certainty should be higher');
      }
    }

    /**
     * Performs some basic validations on the received inputs.
     *
     * @function
     * @memberof commons/mathematical.groupUtils
     * @params inputArray {object} an array.
     * @params group {ZpSubgroup} a Zp subgroup.
     * @params numMembersRequired {number} an integer value.
     * @throws CryptoLibException
     *             if any of the inputs fail any of the validations.
     */
    function validateExtractFromListInputs(
        inputArray, group, numMembersRequired) {
      validateArray(inputArray);
      validateGroup(group);

      if (typeof numMembersRequired === 'undefined') {
        throw new exceptions.CryptoLibException(
            'The given objects should be initialized');
      }

      if (typeof numMembersRequired !== 'number') {
        throw new exceptions.CryptoLibException(
            'The given objects are not from the expected type');
      }

      if (numMembersRequired < 1 || numMembersRequired > inputArray.length) {
        throw new exceptions.CryptoLibException(
            'The given number of required elements cannot be higher than the number of elements of the array');
      }
    }

    /**
     * Validates that all of the exponents in the received list are
     * exponents are members of the received group, by checking if the
     * groups that they contain are equal.
     *
     * @function
     * @memberof commons/mathematical.groupUtils
     * @params group {ZpSubgroup} a Zp subgroup.
     * @params exponents {object} a list of exponents.
     */
    function validateExponents(q, exponents) {
      for (var i = 0; i < exponents.length; i++) {
        if (!exponents[i].getQ().equals(q)) {
          throw new exceptions.CryptoLibException(
              'The list of exponents contained an exponent which does not belong to the group with the same order.');
        }
      }
    }

    return {
      buildZpSubgroupFromPAndG: buildZpSubgroupFromPAndG,
      buildRandomZpSubgroupFromLengthOfP: buildRandomZpSubgroupFromLengthOfP,
      extractNumberOfGroupMembersFromListOfPossibleMembers:
          extractNumberOfGroupMembersFromListOfPossibleMembers,
      generateRandomExponent: generateRandomExponent,
      generateGroupElementSecretKey: generateGroupElementSecretKey,
      compressGroupElements: compressGroupElements,
      buildListWithCompressedFinalElement: buildListWithCompressedFinalElement,
      deserializeGroup: deserializeGroup,
      deserializeGroupElement: deserializeGroupElement,
      deserializeExponent: deserializeExponent,
      compressExponents: compressExponents,
      buildListWithCompressedFinalExponent:
          buildListWithCompressedFinalExponent,
      divide: divide,
      exponentiateArrays: exponentiateArrays
    };
  })();
};

/*
 * 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
 */
cryptolib.modules.commons = cryptolib.modules.commons || {};

/** namespace commons/utils */
cryptolib.modules.commons.utils = function(box) {
  'use strict';

  box.commons = box.commons || {};
  /**
   * A module that holds utility functionalities.
   *
   * @exports commons/utils
   */
  box.commons.utils = {};

  var exceptions;
  cryptolib('commons.exceptions', function(box) {
    exceptions = box.commons.exceptions;
  });

  /**
   * Defines some useful functions for data manipulation and type conversion.
   *
   * @class
   */
  box.commons.utils.Converters = function() {};

  box.commons.utils.Converters.prototype = {
    /**
     * Base64 encodes some data.
     *
     * @function
     * @param {string}
     *            data data to Base64 encode.
     * @param {number}
     *            lineLength line length of encoding (No line breaks if not
     *            specified).
     * @returns {string} base64 encoded data.
     */
    base64Encode: function(data, lineLength) {
      if (lineLength === undefined || lineLength === null) {
        return forge.util.encode64(data);
      } else {
        return forge.util.encode64(data, lineLength);
      }
    },

    /**
     * Base64 decodes some data.
     *
     * @function
     * @param {string}
     *            dataB64 data to Base64 decode.
     * @returns {string} base64 decoded data.
     */
    base64Decode: function(dataB64) {
      return forge.util.decode64(dataB64);
    },

    /**
     * Converts a string to a collection of bytes.
     *
     * @function
     * @param {string}
     *            str string to convert to collection of bytes.
     * @returns collection of bytes from string.
     */
    bytesFromString: function(str) {
      var bytes = [];
      for (var i = 0; i < str.length; i++) {
        bytes.push(str.charCodeAt(i));
      }

      return bytes;
    },

    /**
     * Converts a collection of bytes to a string.
     *
     * @function
     * @param {string}
     *            bytes collection of bytes to convert to string.
     * @return {string} string from collection of bytes.
     */
    bytesToString: function(bytes) {
      var string = '';

      for (var i = 0; i < bytes.length; i++) {
        string += String.fromCharCode(bytes[i]);
      }

      return string;
    },

    /**
     * Converts a string to a hexadecimal string.
     *
     * @function
     * @param {string}
     *            str String to convert.
     * @returns string string in hexadecimal format.
     */
    hexFromString: function(str) {
      return forge.util.bytesToHex(str);
    },

    /**
     * Converts a hexadecimal string to a string.
     *
     * @function
     * @param {string}
     *            hexStr hexadecimal string to convert.
     * @returns {string} String from conversion.
     */
    hexToString: function(hexStr) {
      return forge.util.hexToBytes(hexStr);
    },


    /**
     * Converts a BigInteger object to a base64 string.
     *
     * @function
     * @param {object}
     *            bigInteger forge.jsbn.BigInteger to convert.
     * @returns {string} base64 string from conversion.
     */
    base64FromBigInteger: function(bigInteger) {
      var array = new Uint8Array(bigInteger.toByteArray());
      return box.forge.util.binary.base64.encode(array);
    },

    /**
     * Converts a base64 string to a BigInteger object.
     *
     * @function
     * @param {string}
     *            base64Str base64 string to convert.
     * @returns {object} forge.jsbn.BigInteger from conversion.
     */
    base64ToBigInteger: function(base64Str) {
      var buffer = this.base64Decode(base64Str);
      var hex = this.hexFromString(buffer);
      return new forge.jsbn.BigInteger(hex, 16);
    }
  };

  /**
   * Defines a set of bit operator utilities.
   *
   * @class
   */
  box.commons.utils.BitOperators = function() {};

  box.commons.utils.BitOperators.prototype = {

    /**
     * Bit-wise concatenates some data to some existing data.
     *
     * @function
     * @param {string}
     *            data1 existing data, as string.
     * @param {string}
     *            data2 data to concatenate, as string.
     * @returns {string} bit-wise concatenation of data, as string.
     */
    concatenate: function(data1, data2) {
      try {
        var converters = new box.commons.utils.Converters();
        var data1Bytes = converters.bytesFromString(data1);
        var data2Bytes = converters.bytesFromString(data2);
        var concatDataBytes = data1Bytes.concat(data2Bytes);

        return converters.bytesToString(concatDataBytes);
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'Data could not be bit-wise concatenated.', error);
      }
    },

    /**
     * Unpacks a data subset from a bitwise concatenation of data.
     *
     * @function
     * @param {string}
     *            concatData bit-wise concatenation of data, as string.
     * @param {number}
     *            offset offset of data subset in concatenation.
     * @param {number}
     *            size size of data subset.
     * @returns {string} data subset, as string.
     */
    extract: function(concatData, offset, size) {
      try {
        var converters = new box.commons.utils.Converters();

        var concatDataBytes = converters.bytesFromString(concatData);
        var dataSubsetBytes = concatDataBytes.slice(offset, size);

        return converters.bytesToString(dataSubsetBytes);
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'Data could not be bit-wise extracted.', error);
      }
    }
  };

  /**
   * Defines a set of time utilities.
   *
   * @class
   */
  box.commons.utils.TimeUtils = function() {};

  box.commons.utils.TimeUtils.prototype = {

    /**
     * Generates a time stamp in Unix time, i.e. the number of seconds that
     * have elapsed since 00:00:00 Coordinated Universal Time (UTC), 1
     * January 1970.
     *
     * @function
     * @returns {number} time stamp in seconds, as integer.
     */
    unixTimeStamp: function() {
      try {
        return Math.round(new Date().getTime() / 1000);
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'Could not generate Unix timestamp.', error);
      }
    },

    /**
     * Generates a time stamp in Unix time, i.e. the number of seconds that
     * have elapsed since 00:00:00 Coordinated Universal Time (UTC), 1
     * January 1970. The value is returned as a floating point number.
     *
     * @function
     * @returns {number} time stamp in seconds, as floating point number.
     */
    unixTimeStampFloat: function() {
      try {
        return new Date().getTime() / 1000;
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'Could not generate Unix timestamp.', error);
      }
    }
  };

  /**
   * Defines a set of xpath utilities.
   *
   * @class
   * @param {string}
   *            path path in XPathForm notation Eg.
   * <form><name>test</name></form> to '/form/name'
   */
  box.commons.utils.XPath = function(xml, path) {
    this.path = path;
    this.node = xml;

    var pathTree = path ? path.split('/') : [];

    for (var i = 0; i < pathTree.length; i++) {
      if (pathTree[i] && this.node) {
        var found = false;

        for (var j = 0; j < this.node.childNodes.length; j++) {
          var child = this.node.childNodes[j];

          // we found the node
          if (child.nodeType === Node.ELEMENT_NODE &&
              child.tagName.toLowerCase() === pathTree[i].toLowerCase()) {
            this.node = child;
            found = true;
            break;
          }
        }

        // invalid xpath
        if (!found) {
          this.node = null;
          throw new exceptions.CryptoLibException('Invalid xpath for: ' + path);
        }
      }
    }
  };

  box.commons.utils.XPath.prototype = {
    /**
     * Gets the value of a node. For example <node>value</node> returns
     * value.
     *
     * @function
     * @returns {string} the value, as string.
     */
    getValue: function() {
      return this.node ? this.node.childNodes[0].nodeValue : '';
    },

    /**
     * Returns the value of an attribute of a node. For example <node
     * attr="attr value">node value</node> returns "attr value".
     *
     * @function
     * @param {object}
     *            attribute attribute to get the value.
     * @returns attribute value, as string.
     */
    getAttribute: function(attribute) {
      return this.node ? this.node.getAttribute(attribute) : '';
    },

    /**
     * Gets the node itself-
     *
     * @function
     * @returns {object} xml document, as a DOM object.
     */
    getXml: function() {
      return this.node;
    },

    /**
     * Gets an array of child nodes
     *
     * @function
     * @returns {object} xml document array with all children as DOM
     *          objects.
     */
    getChildren: function() {
      return this.node ? this.node.childNodes : '';
    }
  };

  /**
   * Defines a set of string utilities.
   *
   * @class
   */
  box.commons.utils.StringUtils = function() {

  };

  box.commons.utils.StringUtils.prototype = {
    /**
     * Check if a string is within another string
     *
     * @function
     * @param {string}
     *            main text
     * @param {string}
     *            substring substring to search in the main text
     * @param caseSensitive
     *            boolean, case sensitive the search
     *
     * @returns {boolean} boolean, true if found
     */
    containsSubString: function(string, substring, caseSensitive) {
      var main = string, sub = substring;

      if (!caseSensitive) {
        main = main.toLowerCase();
        sub = sub.toLowerCase();
      }

      return main.indexOf(sub) > -1;
    }
  };

  /**
   * Defines a set of parser utilities.
   *
   * @class
   */
  box.commons.utils.Parsers = function() {};

  box.commons.utils.Parsers.prototype = {

    /**
     * Converts a string to an XML document.
     *
     * @function
     * @param {string}
     *            str String to convert.
     * @returns {string} XML document, as string.
     */
    xmlFromString: function(str) {

      if (this.isIE()) {
        // Remove null terminating character if exists
        var strNoNull = this.removeNullTerminatingChar(str);

        var xmlDoc = new window.ActiveXObject('Microsoft.XMLDOM');
        xmlDoc.async = false;
        xmlDoc.loadXML(strNoNull);
        return xmlDoc;
      } else if (typeof window.DOMParser !== 'undefined') {
        return (new window.DOMParser()).parseFromString(str, 'text/xml');
      } else {
        throw new exceptions.CryptoLibException(
            'XML string could not be parsed.');
      }
    },

    /**
     * Converts an XML document to a string.
     *
     * @fuction
     * @param {string}
     *            xmlDoc xml document to convert, as string.
     * @param {boolean}
     *            removeSelfClosingTags removes self closing tags to
     *            explicit tags. Example: <tag/> to <tag></tag>
     * @returns {string} string from conversion.
     */
    xmlToString: function(xmlDoc, removeSelfClosingTags) {

      var result = this.xmlToStringIeCompatible(xmlDoc);

      if (removeSelfClosingTags) {
        result = this.xmlRemoveSelfClosingTags(result);
      }

      return result;
    },

    xmlRemoveSelfClosingTags: function(data) {

      var split = data.split('/>');
      var newXml = '';
      for (var i = 0; i < split.length - 1; i++) {
        var edsplit = split[i].split('<');
        newXml +=
            split[i] + '></' + edsplit[edsplit.length - 1].split(' ')[0] + '>';
      }

      return newXml + split[split.length - 1];
    },

    xmlToStringIeCompatible: function(xmlNode) {

      if (this.isIE()) {
        return xmlNode.xml;
      } else if (typeof window.XMLSerializer !== 'undefined') {
        return (new window.XMLSerializer()).serializeToString(xmlNode);
      } else {
        throw new exceptions.CryptoLibException(
            'Error while tring to construct XML from String.');
      }
    },

    /**
     * Removes all newline characters from some data.
     *
     * @function
     * @param {string}
     *            data data from which to remove all newline characters, as
     *            string.
     * @returns {string} data with all newline characters removed, as
     *          string.
     */
    removeNewLineChars: function(data) {
      return data.replace(/(\r\n|\n|\r)/gm, '');
    },

    /**
     * Removes all carriage return characters from some data.
     *
     * @param data
     *            Data from which to remove all carriage return characters,
     *            as string.
     * @return Data with all carriage return characters removed, as string.
     */
    removeCarriageReturnChars: function(data) {
      return data.replace(/(\r)/gm, '');
    },

    /**
     * Removes the null terminating character from some data.
     *
     * @function
     * @param {string}
     *            data Data from which to remove the null terminating
     *            character, as string.
     * @returns {string} data with null terminating character removed, as
     *          string.
     */
    removeNullTerminatingChar: function(data) {
      return data.replace(/\x00+$/, '');
    },

    /**
     * Removes the XML header from a string representing an XML.
     *
     * @param data
     *            The XML string from which to remove the XML header.
     * @return XML string with the XML header removed.
     */
    removeXmlHeaderFromString: function(data) {
      return data.replace('<?xml version="1.0" encoding="UTF-8"?>', '');
    },

    /**
     * Removes the signature node from a string representing an XML.
     *
     * @param data
     *            The XML string from which to remove the signature node.
     * @return XML string with the signature node removed.
     */
    removeXmlSignature: function(data) {
      return data.replace(/<Signature[\s\S]*?Signature>/g, '');
    },

    /**
     * Detects if the client agent is Microsoft Internet Explorer. This has
     * been tested to work with Internet Explorer 9, 10 and 11. If this
     * software is to be used on other versions, then it should be checked
     * if those versions are supported by this function and if they are not
     * then this function should be updated.
     */
    isIE: function() {
      var ua = window.navigator.userAgent;
      var msie = ua.indexOf('MSIE ');

      if (msie > 0 || !!navigator.userAgent.match(/Trident.*rv\:11\./)) {
        return true;
      } else {
        return false;
      }
    }
  };

  /**
   * Defines a set of connectors.
   *
   * @class
   */
  box.commons.utils.Connectors = function() {};

  box.commons.utils.Connectors.prototype = {

    /**
     * Posts a request message to a destination via HTTP.
     *
     * @function
     * @param {string}
     *            request request message to post, as string.
     * @param {string}
     *            url url of request destination, as string.
     * @param {boolean}
     *            isAsynch boolean indicating whether the post is
     *            asynchronous.
     * @return {string} response retrieved from request destination, as
     *         string.
     */
    postHttpRequestMessage: function(request, url, isAsynch) {
      // Check if a post destination was provided.
      if (url === '') {
        throw new exceptions.CryptoLibException(
            'No URL provided for posting HTTP request.');
      }

      // Create HTTP request object.
      var httpRequest;
      var done;
      if (window.XMLHttpRequest) {
        httpRequest = new XMLHttpRequest();
        done = XMLHttpRequest.DONE;
      } else {
        throw new exceptions.CryptoLibException(
            'Could not create HTTP request object for this browser.');
      }

      // Define post response message handling.
      var response;
      httpRequest.onreadystatechange = function() {
        if (httpRequest.readyState === done) {
          if (httpRequest.status === 200) {
            response = httpRequest.responseText;
          } else {
            var errorMsg =
                'HTTP request message POST was not successful.\n Status: ' +
                httpRequest.status + ', Message: ' + httpRequest.statusText;
            throw new exceptions.CryptoLibException(errorMsg);
          }
        }
      };

      // Open HTTP request object for synchronous post.
      httpRequest.open('POST', url, isAsynch);

      // Post HTTP request message.
      try {
        httpRequest.send(request);
      } catch (error) {
        var errorMsg =
            'Could not post HTTP request message; IP of web application might be incorrect.';
        throw new exceptions.CryptoLibException(errorMsg, error);
      }

      return response;
    }

  };

  /**
   * Defines a progress meter.
   *
   * @param {integer}
   *            progressMax Maximum amount of progress to be attained.
   * @param {integer}
   *            progressCallback Progress callback function.
   * @param {integer}
   *            progressPercentMinCheckInterval Progress percentage minimum
   *            check interval (optional) (default: 10%).
   * @class
   */
  box.commons.utils.ProgressMeter = function(
      progressMax, progressCallback, progressPercentMinCheckInterval) {
    this.progressMax = progressMax;
    this.progressCallback = progressCallback;
    this.progressPercentMinCheckInterval =
        progressPercentMinCheckInterval || 10;

    this.lastProgressPercent = 0;
  };

  box.commons.utils.ProgressMeter.prototype = {

    /**
     * Calculates the progress as a percentage of the total and provides it
     * as input to the provided callback function.
     *
     * @function
     * @param {integer}
     *            progress Present amount of progress.
     */
    update: function(progress) {
      if (typeof this.progressCallback === 'undefined') {
        return;
      }

      var progressPercent = Math.floor((progress / this.progressMax) * 100);
      progressPercent = Math.min(progressPercent, 100);

      var progressPercentChange = progressPercent - this.lastProgressPercent;

      if (progressPercentChange > 0) {
        var checkProgress =
            (progressPercentChange >= this.progressPercentMinCheckInterval) ||
            (progressPercent === 100);
        if (checkProgress) {
          this.lastProgressPercent = progressPercent;
          this.progressCallback(progressPercent);
        }
      }
    }
  };
};

/*
 * 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
 */
var cryptoPRNG = (function() {
  'use strict';
  return {
    _collectorsStarted: false,
    _collectors: [],
    _events: [],
    _entropy: '',
    _entropyCounter: 0,
    _maxEntropyCounter: 0,
    _collectorHashUpdater: null,
    _hashUpdaterInterval: 0,
    _fullEntropyCallback: null,
    _tools: null,
    _seedLength: 0,
    _prng: null,
    _privateKeyPem: '',
    _pinEntropy: 0,
    _usePrivateKeyEntropy: false,

    /**
     * Initializes all the round object fields.
     *
     * @function
     * @param hashUpdaterInterval
     *            the interval, in millis, that the function that replaces the
     *            current entropy content by an hash is called. By default it is
     *            initialized to 5000.
     * @param maxEntropyCounter
     *            number of maximum bits used to determine when the collectors
     *            should stop collecting entropy. By default it is initialized
     *            to 256.
     */
    _initAndStart: function(hashUpdaterInterval, fullEntropyCallback) {

      this._collectorsStarted = false;
      this._collectors = [];
      this._events = [];
      this._entropy = '';
      this._entropyCounter = 0;
      this._maxEntropyCounter = 256;

      this._collectorHashUpdater = null;
      this._hashUpdaterInterval =
          (hashUpdaterInterval ? hashUpdaterInterval : 1000);
      this._fullEntropyCallback = fullEntropyCallback;
      this._tools = cryptoPRNG.Tools.init();

      this._initCollectors();
      this._startCollectors();
    },

    /**
     * adds a new collector to the list of available connectors
     *
     * @param collector
     *            must return an object that implements the startCollector and
     *            stopCollector methods
     */
    _addCollector: function(collector) {
      this._collectors.push(collector);
    },

    /**
     * adds a new event to the list of available events
     *
     * @param type
     *            event type
     * @param listener
     *            function that will be executed
     */
    _addEvent: function(type, listener) {
      this._events.push({'type': type, 'listener': listener});
    },

    /**
     * Initializes the random generator with the web kit random collector if
     * available.
     *
     * If it is not, it considers the rest of collectors: ajax calls collector,
     * JS calls execution collector, navigator information collector, math
     * random collector, web kit random collector, mouse events collectors, key
     * events collectors, load event collector, scroll collector)
     */
    _initCollectors: function() {
      if (this._noWindow()) {
        this._addNonWindowEntropyCollectors();
        return;
      }

      var _crypto = this._getCrypto();

      if ((_crypto) && (_crypto.getRandomValues)) {
        this._addCollector(cryptoPRNG.Collectors.getWebKitRandomCollector(
            _crypto, this._maxEntropyCounter));
      } else {
        this._addNonWindowEntropyCollectors();

        // The API detects and delivers accelerometer data 50 times per
        // second
        if (window.DeviceOrientationEvent) {
          // Listen for the device orientation event and handle
          // DeviceOrientationEvent object
          this._addEvent(
              'deviceorientation',
              cryptoPRNG.Collectors.getDeviceOrientationCollector);
        } else if (window.OrientationEvent) {
          // Listen for the MozOrientation event and handle
          // OrientationData object
          this._addEvent(
              'MozOrientation',
              cryptoPRNG.Collectors.getDeviceOrientationCollector);
        }
        if (window.DeviceMotionEvent) {
          this._addEvent(
              'devicemotion', cryptoPRNG.Collectors.getDeviceMotionCollector);
        }
      }
    },

    _addNonWindowEntropyCollectors: function() {
      if (this._usePrivateKeyEntropy) {
        this._addCollector(cryptoPRNG.Collectors.getPrivateKeyCollector(
            this._privateKeyPem, this._pinEntropy));
      }
      this._addCollector(cryptoPRNG.Collectors.getAjaxCollector());
      this._addCollector(cryptoPRNG.Collectors.getJSExecutionCollector());
      this._addCollector(cryptoPRNG.Collectors.getNavigatorInfoCollector());
      this._addCollector(cryptoPRNG.Collectors.getMathRandomCollector());
      // mouse events collectors
      this._addEvent('mousemove', cryptoPRNG.Collectors.getMouseMoveCollector);
      this._addEvent(
          'mousewheel', cryptoPRNG.Collectors.getMouseWheelCollector);
      this._addEvent('mouseup', cryptoPRNG.Collectors.getMouseUpCollector);
      this._addEvent('mousedown', cryptoPRNG.Collectors.getMouseDownCollector);

      this._addEvent(
          'touchstart', cryptoPRNG.Collectors.getTouchStartCollector);
      this._addEvent('touchmove', cryptoPRNG.Collectors.getTouchMoveCollector);
      this._addEvent('touchend', cryptoPRNG.Collectors.getTouchEndCollector);

      this._addEvent(
          'gesturestart', cryptoPRNG.Collectors.getGestureStartCollector);
      this._addEvent(
          'gestureend', cryptoPRNG.Collectors.getGestureEndCollector);

      // keyboard events collectors
      this._addEvent('keyup', cryptoPRNG.Collectors.getKeyUpCollector);
      this._addEvent('keydown', cryptoPRNG.Collectors.getKeyDownCollector);
      // page events collectors
      this._addEvent('load', cryptoPRNG.Collectors.getLoadCollector);
      this._addEvent('scroll', cryptoPRNG.Collectors.getScrollCollector);
      // requests collector collectors
      this._addEvent('beforeload', cryptoPRNG.Collectors.getRequestsCollector);
    },

    _getCrypto: function() {
      return window.crypto || window.msCrypto;
    },

    _noWindow: function() {
      return (typeof window === 'undefined');
    },

    /**
     * Start the collectors
     */
    _startCollectors: function() {
      if (this._collectorsStarted) {
        return;
      }

      var i = 0;
      // start all the collectors
      while (i < this._collectors.length) {
        try {
          this._collectors[i].startCollector();
        } catch (e) {
          // do not do anything about any exception that is thrown
          // by the execution of the collectors
        }
        i++;
      }

      i = 0;
      // start all the events
      while (i < this._events.length) {
        try {
          if (window.addEventListener) {
            window.addEventListener(
                this._events[i].type, this._events[i].listener, false);
          } else if (document.attachEvent) {
            document.attachEvent(
                'on' + this._events[i].type, this._events[i].listener);
          }
        } catch (e) {
          // do not do anything about any exception that is thrown
          // by the execution of the events
        }
        i++;
      }

      this._tools._mdUpdate.start();
      this._collectorHashUpdater = setInterval(function() {
        cryptoPRNG._hashUpdater();
      }, this._hashUpdaterInterval);

      this._collectorsStarted = true;
    },

    /**
     * Stop the collectors
     */
    _stopCollectors: function() {
      if (!this._collectorsStarted) {
        return;
      }

      var i = 0;
      while (i < this._collectors.length) {
        try {
          this._collectors[i].stopCollector();
        } catch (e) {
          // do not do anything about any exception that is thrown
          // by the execution of the collectors
        }
        i++;
      }

      i = 0;
      while (i < this._events.length) {
        try {
          if (window.removeEventListener) {
            window.removeEventListener(
                this._events[i].type, this._events[i].listener, false);
          } else if (document.detachEvent) {
            document.detachEvent(
                'on' + this._events[i].type, this._events[i].listener);
          }
        } catch (e) {
          // do not do anything about any exception that is thrown
          // by the remove of the events
        }
        i++;
      }

      if (this._collectorHashUpdater) {
        clearInterval(this._collectorHashUpdater);
      }

      this._collectorsStarted = false;
    },

    /**
     * Usually this method is called from the collectors and events. It adds
     * entropy to the already collected entropy and increments the entropy
     * counter
     *
     * @param data
     *            the entropy data to be added
     * @param entropyCounter
     *            the entropy counter to be added
     */
    _collectEntropy: function(data, entropyCounter) {
      this._entropy += data;
      this._entropyCounter += (entropyCounter ? entropyCounter : 0);

      // if the entropy counter has reach the limit, update the hash and stop
      // the collectors
      if (this._entropyCounter >= this._maxEntropyCounter &&
          this._collectorsStarted) {
        this._hashUpdater();
      }
    },

    /**
     * Sets the entropy data with the hash that is created using the current
     * entropy data value. If the entropy counter has reached the max entropy
     * counter set, it stops the collectors
     *
     * @return the generated entropy hash
     */
    _hashUpdater: function() {
      var entropyHash = this._updateEntropyHash();

      if (this._entropyCounter >= this._maxEntropyCounter &&
          this._collectorsStarted) {
        entropyHash = this._stopCollectEntropy();

        var newPRNG = new cryptoPRNG.PRNG(entropyHash, this._tools);

        if (this._fullEntropyCallback) {
          this._fullEntropyCallback(newPRNG);
        } else {
          this._prng = newPRNG;
        }
      }
      return entropyHash;
    },

    _stopCollectEntropy: function() {
      // stop collecting entropy
      this._stopCollectors();
      var entropyHash = this._updateEntropyHash();
      this._entropy = '';
      this._entropyCounter = 0;

      return entropyHash;
    },

    _updateEntropyHash: function() {
      var digester = this._tools._mdUpdate;
      digester.update(this._entropy);
      var entropyHash = forge.util.bytesToHex(digester.digest().getBytes());
      this._entropy = entropyHash;
      return entropyHash;
    },

    /**
     * Set Private Key and an Entropy it adds to be used to collect Entropy. The
     * amount of randomness(entropy) depends on the pin that is used for Private
     * Key generating.
     *
     * @function
     * @param privateKey
     *            {string} private key, as string in PEM format.
     * @param pinEntropy
     *            the amount of randomness that can be extracted from the
     *            Private Key.
     */
    setDataForPrivateKeyEntropyCollector: function(privateKeyPem, pinEntropy) {
      if (privateKeyPem && (!isNaN(pinEntropy)) && (pinEntropy > 0)) {
        this._privateKeyPem = privateKeyPem;
        this._pinEntropy = pinEntropy;
        this._usePrivateKeyEntropy = true;
      }
    },

    /**
     * Initializes collectors and starts them. Set Private Key and Entropy
     * before if this information should be used to collect Entropy.
     */
    startEntropyCollection: function(entropyCollectorCallback) {
      var hashUpdaterInterval = 1000;

      this._initAndStart(hashUpdaterInterval, entropyCollectorCallback);
    },

    /**
     * In the case that the maximum amount of entropy had been reached before
     * calling this method, it does not do anything, and a false is returned.
     * Otherwise, this method creates a PRNG if the collectors have not gathered
     * the maximum amount of entropy. In addition, it stops the collectors.
     *
     * This method return a true if the maximum entropy was already reached and
     * false in other case.
     */
    stopEntropyCollectionAndCreatePRNG: function() {
      var generatedWithMaximumEntropy = true;

      if (!this._prng) {
        var entropyHash = this._stopCollectEntropy();
        this._prng = new cryptoPRNG.PRNG(entropyHash, this._tools);
        generatedWithMaximumEntropy = false;
      }

      return generatedWithMaximumEntropy;
    },

    /**
     * This method creates a PRNG from a given seed, in Hexadecimal format. This
     * method is to be used from a Worker, which has received the seed from the
     * main thread.
     *
     * @param seed
     *            A string of 64 characters. It represents a 32-byte array in
     *            Hexadecimal.
     *
     */
    createPRNGFromSeed: function(seed) {
      this._prng = new cryptoPRNG.PRNG(seed, this._tools);
    },

    /**
     * Generates a random array of bytes, which is then formatted to hexadecimal
     * This method is to be called from the main thread, and the given string is
     * to be passed to a Worker.
     */
    generateRandomSeedInHex: function(lengthSeed) {
      this._seedLength = (lengthSeed ? lengthSeed : 32);
      var seedInBytes = this._prng.generate(this._seedLength);

      return forge.util.bytesToHex(seedInBytes);
    },

    getPRNG: function() {
      if (!this._prng) {
        throw new Error('The PRNG has not been initialized yet');
      }

      return this._prng;
    },

    getEntropyCollectedAsPercentage: function() {
      if (this._entropyCounter >= this._maxEntropyCounter) {
        return 100;
      }
      return this._entropyCounter * 100 / this._maxEntropyCounter;
    }
  };
})();

cryptoPRNG.Tools = (function() {
  'use strict';
  return {
    _mdUpdate: null,
    _mdReseed: null,
    _formatKey: null,
    _formatSeed: null,
    _cipher: null,

    init: function() {
      this._mdUpdate = forge.md.sha256.create();
      this._mdReseed = forge.md.sha256.create();
      this._formatKey = function(key) {
        // convert the key into 32-bit integers
        var tmp = forge.util.createBuffer(key);
        key = [
          tmp.getInt32(),
          tmp.getInt32(),
          tmp.getInt32(),
          tmp.getInt32(),
          tmp.getInt32(),
          tmp.getInt32(),
          tmp.getInt32(),
          tmp.getInt32()          
        ];
        // return the expanded key
        return forge.aes._expandKey(key, false);
      };
      this._formatSeed = function(seed) {
        // convert seed into 32-bit integers
        var tmp = forge.util.createBuffer(seed);
        seed = [
          tmp.getInt32(),
          tmp.getInt32(),
          tmp.getInt32(),
          tmp.getInt32()
        ];
        return seed;
      };
      this._cipher = function(key, counter) {
        var aes_output = [];
        forge.aes._updateBlock(
            this._formatKey(key), this._formatSeed('' + counter), aes_output,
            false);
        var aes_buffer = forge.util.createBuffer();
        aes_buffer.putInt32(aes_output[0]);
        aes_buffer.putInt32(aes_output[1]);
        aes_buffer.putInt32(aes_output[2]);
        aes_buffer.putInt32(aes_output[3]);
        return aes_buffer.getBytes();
      };

      return this;
    }
  };
})();

cryptoPRNG.PRNG = function(entropyHash, tools) {
  'use strict';
  this._key = '';
  this._counter = 0;
  this._tools = null;
  if (tools) {
    this._tools = tools;
  } else {
    this._tools = cryptoPRNG.Tools.init();
  }
  this._entropyHash = entropyHash;
};

cryptoPRNG.PRNG.prototype = (function() {
  'use strict';
  return {
    /**
     * Generates a random array of bytes using the gathered entropy data.
     *
     * @param count
     *            the total number of random bytes to generate
     */
    generate: function(count) {
      var keyAux;

      if (this._key === null || this._key === '') {
        this._reseed();
      }

      // buffer where to store the random bytes
      var b = forge.util.createBuffer();
      var limitReach = 1;
      while (b.length() < count) {
        b.putBytes(this._tools._cipher(this._key, this._counter));
        this._counter++;

        if (b.length >= (limitReach * Math.pow(2, 20))) {
          keyAux = this._key;
          this._key = '';
          for (var i = 0; i < 2; i++) {
            this._key += this._tools._cipher(keyAux, this._counter);
            this._counter++;
          }
          limitReach++;
        }
      }

      // do it two times to ensure a key with 256 bits
      keyAux = this._key;
      this._key = '';
      for (var j = 0; j < 2; j++) {
        this._key += this._tools._cipher(keyAux, this._counter);
        this._counter++;
      }

      return b.getBytes(count);
    },

    /**
     * Reseeds the generator
     */
    _reseed: function() {
      var digester = this._tools._mdReseed;
      digester.start();
      digester.update(this._entropyHash);
      digester.update(this._key);
      this._key = forge.util.bytesToHex(digester.digest().getBytes());
      this._counter++;
    }
  };
})();

cryptoPRNG.Collectors = (function() {
  'use strict';

  var MIN_MATH_ROUND_ENTROPY_FACTOR = 1000000;

  function _getMathRoundWithEntropy(data) {
    return Math.round(data * MIN_MATH_ROUND_ENTROPY_FACTOR);
  }

  return {
    getAjaxCollector: function() {
      return {
        startCollector: function() {
          // if jQuery is included
          if (window.jQuery !== undefined) {
            $(window)
                .ajaxStart(function() {
                  cryptoPRNG._collectEntropy((+new Date()), 1);
                })
                .ajaxComplete(function() {
                  cryptoPRNG._collectEntropy((+new Date()), 1);
                });
          }
        },

        stopCollector: function() {
          // if jQuery is included
          if (window.jQuery !== undefined) {
            $(window).unbind('ajaxStart');
            $(window).unbind('ajaxComplete');
          }
          return true;
        }
      };
    },

    getJSExecutionCollector: function() {
      return {
        _collectTimeout: null,

        startCollector: function() {
          var timer_start = (+new Date()), timer_end;

          this._collectTimeout = (function collect() {
            timer_end = (+new Date());
            var total_time = timer_end - timer_start;
            // because of browser baseline time checks we limit it
            if (total_time > 20) {
              cryptoPRNG._collectEntropy((+new Date()), 1);
            }
            timer_start = timer_end;
            return setTimeout(collect, 0);
          })();
        },

        stopCollector: function() {
          clearTimeout(this._collectTimeout);
        }
      };
    },

    getNavigatorInfoCollector: function() {
      return {
        startCollector: function() {
          if (typeof(navigator) !== 'undefined') {
            var _navString = '';
            for (var key in navigator) {
              if (typeof key !== 'undefined') {
                try {
                  if (typeof(navigator[key]) === 'string') {
                    _navString += navigator[key];
                  }
                } catch (e) {
                  // ignore any kind of exception
                }
              }
            }
            cryptoPRNG._collectEntropy(_navString, 1);
          }
        },

        stopCollector: function() {
          // just executed once, so no need to execute nothing more
          return true;
        }
      };
    },

    getMathRandomCollector: function() {
      return {
        startCollector: function() {
          cryptoPRNG._collectEntropy(Math.random(), 0);
        },

        stopCollector: function() {
          // just executed once, so no need to execute nothing more
          return true;
        }
      };
    },

    getPrivateKeyCollector: function(privateKeyPem, pinEntropy) {

      var privateKey = forge.pki.privateKeyFromPem(privateKeyPem);
      return {
        pkCollector: true,
        startCollector: function() {
          cryptoPRNG._collectEntropy(privateKey.d, pinEntropy);
        },

        stopCollector: function() {
          // just executed once, so no need to execute nothing more
          return true;
        }
      };
    },

    getWebKitRandomCollector: function(globalCrypto, entropyQuantity) {
      return {
        startCollector: function() {
          var numPositions = entropyQuantity / 32;
          // get cryptographically strong entropy in Webkit
          var ab = new Uint32Array(numPositions);
          globalCrypto.getRandomValues(ab);
          var data = '';
          for (var i = 0; i < ab.length; i++) {
            data += '' + (ab[i]);
          }
          cryptoPRNG._collectEntropy(data, entropyQuantity);
        },

        stopCollector: function() {
          // just executed once, so no need to execute nothing more
          return true;
        }
      };
    },

    getMouseMoveCollector: function(ev) {
      // to ensure compatibility with IE 8
      ev = ev || window.event;
      cryptoPRNG._collectEntropy(
          (ev.x || ev.clientX || ev.offsetX || 0) +
              (ev.y || ev.clientY || ev.offsetY || 0) + (+new Date()),
          3);
    },

    getMouseWheelCollector: function(ev) {
      ev = ev || window.event;
      cryptoPRNG._collectEntropy((ev.offsetY || 0) + (+new Date()), 3);
    },

    getMouseDownCollector: function(ev) {
      ev = ev || window.event;
      cryptoPRNG._collectEntropy(
          (ev.x || ev.clientX || ev.offsetX || 0) +
              (ev.y || ev.clientY || ev.offsetY || 0) + (+new Date()),
          3);
    },

    getMouseUpCollector: function(ev) {
      ev = ev || window.event;
      cryptoPRNG._collectEntropy(
          (ev.x || ev.clientX || ev.offsetX || 0) +
              (ev.y || ev.clientY || ev.offsetY || 0) + (+new Date()),
          3);
    },

    getTouchStartCollector: function(ev) {
      ev = ev || window.event;
      cryptoPRNG._collectEntropy(
          (ev.touches[0].pageX || ev.clientX || 0) +
              (ev.touches[0].pageY || ev.clientY || 0) + (+new Date()),
          5);
    },

    getTouchMoveCollector: function(ev) {
      ev = ev || window.event;
      cryptoPRNG._collectEntropy(
          (ev.touches[0].pageX || ev.clientX || 0) +
              (ev.touches[0].pageY || ev.clientY || 0) + (+new Date()),
          3);
    },

    getTouchEndCollector: function() {
      cryptoPRNG._collectEntropy((+new Date()), 1);
    },

    getGestureStartCollector: function(ev) {
      ev = ev || window.event;
      cryptoPRNG._collectEntropy(
          (ev.touches[0].pageX || ev.clientX || 0) +
              (ev.touches[0].pageY || ev.clientY || 0) + (+new Date()),
          5);
    },

    getGestureEndCollector: function() {
      cryptoPRNG._collectEntropy((+new Date()), 1);
    },

    motionX: null,
    motionY: null,
    motionZ: null,

    getDeviceMotionCollector: function(ev) {
      ev = ev || window.event;
      var acceleration = ev.accelerationIncludingGravity;
      var currentX = (_getMathRoundWithEntropy(acceleration.x) || 0);
      var currentY = (_getMathRoundWithEntropy(acceleration.y) || 0);
      var currentZ = (_getMathRoundWithEntropy(acceleration.z) || 0);
      var rotation = ev.rotationRate;
      if (rotation !== null) {
        currentX += _getMathRoundWithEntropy(rotation.alpha);
        currentY += _getMathRoundWithEntropy(rotation.beta);
        currentZ += _getMathRoundWithEntropy(rotation.gamma);
      }
      // The API detects and delivers accelerometer data 50 times per
      // second
      // even if there is any event related, so this is
      // a way to control if it really changed or not
      if ((cryptoPRNG.Collectors.motionX === null) ||
          (cryptoPRNG.Collectors.motionY === null) ||
          (cryptoPRNG.Collectors.motionZ === null) ||
          (cryptoPRNG.Collectors.motionX !== currentX) ||
          (cryptoPRNG.Collectors.motionY !== currentY) ||
          (cryptoPRNG.Collectors.motionZ !== currentZ)) {
        cryptoPRNG.Collectors.motionX = currentX;
        cryptoPRNG.Collectors.motionY = currentY;
        cryptoPRNG.Collectors.motionZ = currentZ;
        cryptoPRNG._collectEntropy(
            currentX + currentY + currentZ + (+new Date()), 1);
      }
    },

    deviceOrientationX: null,
    deviceOrientationY: null,
    deviceOrientationZ: null,

    getDeviceOrientationCollector: function(ev) {
      ev = ev || window.event;
      var currentX =
          (_getMathRoundWithEntropy(ev.gamma) ||
           _getMathRoundWithEntropy(ev.x) || 0);
      var currentY =
          (_getMathRoundWithEntropy(ev.beta) ||
           _getMathRoundWithEntropy(ev.y) || 0);
      var currentZ =
          (_getMathRoundWithEntropy(ev.alpha) ||
           _getMathRoundWithEntropy(ev.z) || 0);
      // The API detects and delivers accelerometer data 50 times per
      // second
      // even if there is any event related, so this is
      // a way to control if it really changed or not
      if ((cryptoPRNG.Collectors.deviceOrientationX === null) ||
          (cryptoPRNG.Collectors.deviceOrientationY === null) ||
          (cryptoPRNG.Collectors.deviceOrientationZ === null) ||
          (cryptoPRNG.Collectors.deviceOrientationX !== currentX) ||
          (cryptoPRNG.Collectors.deviceOrientationY !== currentY) ||
          (cryptoPRNG.Collectors.deviceOrientationZ !== currentZ)) {
        cryptoPRNG.Collectors.deviceOrientationX = currentX;
        cryptoPRNG.Collectors.deviceOrientationY = currentY;
        cryptoPRNG.Collectors.deviceOrientationZ = currentZ;
        cryptoPRNG._collectEntropy(
            currentX + currentY + currentZ + (+new Date()), 1);
      }
    },

    getKeyDownCollector: function(ev) {
      ev = ev || window.event;
      cryptoPRNG._collectEntropy((ev.keyCode) + (+new Date()), 3);
    },

    getKeyUpCollector: function(ev) {
      ev = ev || window.event;
      cryptoPRNG._collectEntropy((ev.keyCode) + (+new Date()), 3);
    },

    getLoadCollector: function(ev) {
      ev = ev || window.event;
      cryptoPRNG._collectEntropy((ev.keyCode) + (+new Date()), 3);
    },

    getScrollCollector: function(ev) {
      ev = ev || window.event;
      cryptoPRNG._collectEntropy((ev.keyCode) + (+new Date()), 3);
    },

    getRequestsCollector: function(ev) {
      ev = ev || window.event;
      cryptoPRNG._collectEntropy((ev.keyCode) + (+new Date()), 3);
    }
  };
})();

/*
 * 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
 */
cryptolib.modules = cryptolib.modules || {};

/**
 * Defines the generation and opening of digital envelopes.
 */
cryptolib.modules.digitalenvelope = function(box) {
  'use strict';

  box.digitalenvelope = box.digitalenvelope || {};
  box.digitalenvelope.factory = {};

  var policies = {
    symmetric: {
      secretkey: {
        encryption: {
          lengthBytes: box.policies.digitalenvelope.symmetric.secretkey
                           .encryption.lengthBytes
        },
        mac: {
          lengthBytes:
              box.policies.digitalenvelope.symmetric.secretkey.mac.lengthBytes
        }
      }
    }
  };

  var exceptions;
  var converters;
  var bitOperators;
  var secretKeyFactory;
  var macDigester;
  var symmetricCipher;
  var asymmetricCipher;

  var f = function(box) {
    exceptions = box.commons.exceptions;
    converters = new box.commons.utils.Converters();
    bitOperators = new box.commons.utils.BitOperators();
    secretKeyFactory = new box.symmetric.secretkey.factory.SecretKeyFactory();
    macDigester = (new box.symmetric.mac.factory.MacFactory()).create();
    symmetricCipher =
        (new box.symmetric.cipher.factory.SymmetricCipherFactory())
            .getCryptoSymmetricCipher();
    asymmetricCipher =
        (new box.asymmetric.cipher.factory.AsymmetricCipherFactory())
            .getCryptoAsymmetricCipher();
  };

  f.policies = {
    symmetric: {
      secretkey: {
        encryption: {
          lengthBytes: box.policies.digitalenvelope.symmetric.secretkey
                           .encryption.lengthBytes
        },
        mac: {
          lengthBytes:
              box.policies.digitalenvelope.symmetric.secretkey.mac.lengthBytes
        },
        secureRandom: {
          provider: box.policies.digitalenvelope.symmetric.secretkey
                        .secureRandom.provider
        }
      },
      cipher: {
        provider: box.policies.digitalenvelope.symmetric.cipher.provider,
        algorithm: {
          AES128_GCM: {
            name: box.policies.digitalenvelope.symmetric.cipher.algorithm
                      .AES128_GCM.name,
            keyLengthBytes: box.policies.digitalenvelope.symmetric.cipher
                                .algorithm.AES128_GCM.keyLengthBytes,
            tagLengthBytes: box.policies.digitalenvelope.symmetric.cipher
                                .algorithm.AES128_GCM.tagLengthBytes
          }
        },
        initializationVectorLengthBytes:
            box.policies.digitalenvelope.symmetric.cipher
                .initializationVectorLengthBytes,
        secureRandom: {
          provider: box.policies.digitalenvelope.symmetric.cipher.secureRandom
                        .provider
        }
      },
      mac: {
        hash: box.policies.digitalenvelope.symmetric.mac.hash,
        provider: box.policies.digitalenvelope.symmetric.mac.provider
      }
    },
    asymmetric: {
      cipher: {
        algorithm: box.policies.digitalenvelope.asymmetric.cipher.algorithm,
        secretKeyLengthBytes:
            box.policies.digitalenvelope.asymmetric.cipher.secretKeyLengthBytes,
        ivLengthBytes:
            box.policies.digitalenvelope.asymmetric.cipher.ivLengthBytes,
        tagLengthBytes:
            box.policies.digitalenvelope.asymmetric.cipher.tagLengthBytes,
        deriver: box.policies.digitalenvelope.asymmetric.cipher.deriver,
        hash: box.policies.digitalenvelope.asymmetric.cipher.hash,
        symmetricCipher:
            box.policies.digitalenvelope.asymmetric.cipher.symmetricCipher,
        provider: box.policies.digitalenvelope.asymmetric.cipher.provider,
        secureRandom: {
          provider: box.policies.digitalenvelope.asymmetric.cipher.secureRandom
                        .provider
        }
      }
    }
  };

  cryptolib('commons', 'symmetric', 'asymmetric.cipher', f);

  /**
   * Defines a factory for the creation of digital envelope generators and
   * openers.
   *
   * @class DigitalEnvelopeFactory
   * @memberof digitalenvelope
   */
  box.digitalenvelope.factory.DigitalEnvelopeFactory = function() {

  };

  box.digitalenvelope.factory.DigitalEnvelopeFactory.prototype = {

    /**
     * Obtains a new digital envelope generator.
     *
     * @function getDigitalEnvelopeGenerator
     * @return {DigitalEnvelopeGenerator} the digital envelope generator.
     */
    getDigitalEnvelopeGenerator: function() {

      try {
        return new box.digitalenvelope.DigitalEnvelopeGenerator();
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'Could not obtain a digital envelope generator.', error);
      }
    },

    /**
     * Obtains new a digital envelope opener.
     *
     * @function getDigitalEnvelopeOpener
     * @return {DigitalEnvelopeOpener} the digital envelope opener.
     */
    getDigitalEnvelopeOpener: function() {

      try {
        return new box.digitalenvelope.DigitalEnvelopeOpener();
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'Could not obtain a digital envelope opener.', error);
      }
    }
  };

  /**
   * Defines a digital envelope generator.
   *
   * @class DigitalEnvelopeGenerator
   * @memberof digitalenvelope
   */
  box.digitalenvelope.DigitalEnvelopeGenerator = function() {

  };

  box.digitalenvelope.DigitalEnvelopeGenerator.prototype = {

    /**
     * Generates a digital envelope for some data. The envelope can be
     * generated with one or more public keys, each of whose corresponding
     * private key can be used to open the envelope.
     *
     * @function generate
     * @param dataBase64
     *            {string} the data to store in the digital envelope, as a
     *            string in Base64 encoded format.
     * @param publicKeysPem
     *            {string[]} a list of one or more public keys used to
     *            generate the digital envelope, as an array of strings in
     *            PEM format.
     * @return {DigitalEnvelope} the generated digital envelope.
     */
    generate: function(dataBase64, publicKeysPem) {

      try {
        validateGeneratorInput(dataBase64, publicKeysPem);

        // Generate encryption secret key and use it to symmetrically encrypt
        // data.
        var secretKeyForEncryptionBase64 =
            secretKeyFactory.getCryptoSecretKeyGeneratorForEncryption()
                .generate();
        var encryptedDataBase64 =
            symmetricCipher.encrypt(secretKeyForEncryptionBase64, dataBase64);

        // Generate MAC secret key and use it to generate MAC of symmetrically
        // Base64 encryption of encrypted data.
        var secretKeyForMacBase64 =
            secretKeyFactory.getCryptoSecretKeyGeneratorForMac().generate();
        var encryptedDataBase64Base64 =
            converters.base64Encode(encryptedDataBase64);
        var macBase64 = macDigester.generate(
            secretKeyForMacBase64, [encryptedDataBase64Base64]);

        // Construct bit-wise concatenation of encryption and MAC secret keys.
        var secretKeyForEncryption =
            converters.base64Decode(secretKeyForEncryptionBase64);
        var secretKeyForMac = converters.base64Decode(secretKeyForMacBase64);
        var secretKeyConcatenation =
            secretKeyForEncryption.toString() + secretKeyForMac.toString();
        var secretKeyConcatenationBase64 =
            converters.base64Encode(secretKeyConcatenation);

        // Generate list of asymmetric encryptions of the secret key
        // concatenation, each encryption created using a different one of the
        // public keys provided as input for the digital envelope generation.
        var secretKeyEncryptions = [];
        for (var i = 0; i < publicKeysPem.length; i++) {
          var publicKeyPem = publicKeysPem[i];
          var encryptedSecretKeyConcatentionBase64 = asymmetricCipher.encrypt(
              publicKeyPem, secretKeyConcatenationBase64);
          secretKeyEncryptions.push(new SecretKeyEncryption(
              encryptedSecretKeyConcatentionBase64, publicKeyPem));
        }

        return new DigitalEnvelope(
            encryptedDataBase64, macBase64, secretKeyEncryptions);
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'Digital envelope could not be generated.', error);
      }
    }
  };

  /**
   * Defines a digital envelope opener.
   *
   * @class DigitalEnvelopeOpener
   * @memberof digitalenvelope
   */
  box.digitalenvelope.DigitalEnvelopeOpener = function() {

    this._getEncryptedSecretKeyConcatenation = function(
        secretKeyEncryptions, privateKeyPem) {

      // Extract modulus and public exponent from private key.
      var privateKey = box.forge.pki.privateKeyFromPem(privateKeyPem);
      var modulus = privateKey.n;
      var publicExponent = privateKey.e;

      for (var i = 0; i < secretKeyEncryptions.length; i++) {
        var publicKeyPem = secretKeyEncryptions[i].getPublicKey();
        var publicKey = box.forge.pki.publicKeyFromPem(publicKeyPem);

        if (publicKey.n.equals(modulus) && publicKey.e.equals(publicExponent)) {
          return secretKeyEncryptions[i].getEncryptedSecretKeyConcatenation();
        }
      }

      throw new exceptions.CryptoLibException(
          'Could not find an asymmetric encryption of the secret key concatenation that could be decrypted using the private key provided.');
    };
  };

  box.digitalenvelope.DigitalEnvelopeOpener.prototype = {

    /**
     * Opens a digital envelope and retrieves its data. The envelope can be
     * opened with any private key corresponding to a public key that was
     * used to generate the envelope.
     *
     * @function open
     * @param digitalEnvelope
     *            {DigitalEnvelope} the digital envelope to open.
     * @param privateKeyPem
     *            {string} the private key used to open the digital
     *            envelope, as a string in PEM format.
     * @return {string} the data stored in the digital envelope, as a string
     *         in Base64 encoded format.
     */
    open: function(digitalEnvelope, privateKeyPem) {

      try {
        validateOpenerInput(digitalEnvelope, privateKeyPem);

        // Retrieve data from digital envelope.
        var encryptedDataBase64 = digitalEnvelope.getEncryptedData();
        var macBase64 = digitalEnvelope.getMac();
        var secretKeyEncryptions = digitalEnvelope.getSecretKeyEncryptions();

        // Retrieve asymmetric encryption of bit-wise concatenation of
        // encryption and MAC secret keys that can be decrypted with private
        // key provided as input.
        var encryptedSecretKeyConcatenationBase64 =
            this._getEncryptedSecretKeyConcatenation(
                secretKeyEncryptions, privateKeyPem);

        // Asymmetrically decrypt bit-wise concatenation of encryption and MAC
        // secret keys.
        var secretKeyConcatenationBase64 = asymmetricCipher.decrypt(
            privateKeyPem, encryptedSecretKeyConcatenationBase64);
        var secretKeyConcatenation =
            converters.base64Decode(secretKeyConcatenationBase64);

        // Extract secret keys from bit-wise concatenation.
        var secretKeyForEncryptionLength =
            policies.symmetric.secretkey.encryption.lengthBytes;
        var secretKeyForMacLength =
            policies.symmetric.secretkey.mac.lengthBytes;
        var secretKeyForEncryption = bitOperators.extract(
            secretKeyConcatenation, 0, secretKeyForEncryptionLength);
        var secretKeyForEncryptionBase64 =
            converters.base64Encode(secretKeyForEncryption);
        var secretKeyForMac = bitOperators.extract(
            secretKeyConcatenation, secretKeyForEncryptionLength,
            secretKeyForEncryptionLength + secretKeyForMacLength);
        var secretKeyForMacBase64 = converters.base64Encode(secretKeyForMac);

        // Check integrity of digital envelope.
        var encryptedDataBase64Base64 =
            converters.base64Encode(encryptedDataBase64);
        if (!macDigester.verify(
                macBase64, secretKeyForMacBase64,
                [encryptedDataBase64Base64])) {
          throw new exceptions.CryptoLibException(
              'Integrity of digital envelope could not be verified.');
        }

        return symmetricCipher.decrypt(
            secretKeyForEncryptionBase64, encryptedDataBase64);
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'Digital envelope could not be opened.', error);
      }
    }
  };

  /**
   * Container class for a digital envelope.
   *
   * @class DigitalEnvelope
   * @param encryptedDataBase64
   *            {string} the symmetrically encrypted data, as a string in
   *            Base64 encoded format.
   * @param macBase64
   *            {string} the MAC of the symmetrically encrypted data, as a
   *            string in Base64 encoded format.
   * @param secretKeyEncryptions
   *            {SecretKeyEncryption[]} the list of asymmetric encryptions of
   *            the secret key concatenation, each encryption having been
   *            created using a different one of the public keys provided as
   *            input to the digital envelope generator.
   * @memberof digitalenvelope
   */
  function DigitalEnvelope(
      encryptedDataBase64, macBase64, secretKeyEncryptions) {
    this.encryptedDataBase64 = encryptedDataBase64;
    this.macBase64 = macBase64;
    this.secretKeyEncryptions = secretKeyEncryptions;

    var _encryptedSecretKeyConcatsBase64 = [];
    var _publicKeysPem = [];
    for (var i = 0; i < secretKeyEncryptions.length; i++) {
      _encryptedSecretKeyConcatsBase64[i] =
          secretKeyEncryptions[i].getEncryptedSecretKeyConcatenation();
      _publicKeysPem[i] = secretKeyEncryptions[i].getPublicKey();
    }

    this._getEncryptedSecretKeyConcatsBase64 = function() {
      return _encryptedSecretKeyConcatsBase64;
    };

    this._getPublicKeysPem = function() {
      return _publicKeysPem;
    };
  }

  DigitalEnvelope.prototype = {

    /**
     * @function getEncryptedData
     * @return {string} the symmetrically encrypted data, as a string in
     *         Base64 encoded format.
     */
    getEncryptedData: function() {

      return this.encryptedDataBase64;
    },

    /**
     * @function getMac
     * @return {string} the MAC of the symmetrically encrypted data, as a
     *         string in Base64 encoded format.
     */
    getMac: function() {

      return this.macBase64;
    },

    /**
     * @function getSecretKeyEncryptions
     * @return {SecretKeyEncryption[]} the list of asymmetric encryptions of
     *         the secret key concatenation.
     */
    getSecretKeyEncryptions: function() {

      return this.secretKeyEncryptions;
    },

    /**
     * Generates a string representation of the digital envelope.
     * <p>
     * Note: in order to permit interoperability between libraries, this
     * representation should match the equivalent representation in Java.
     *
     * @function stringify
     * @return {string} the string representation of the digital envelope.
     */
    stringify: function() {

      return JSON.stringify({
        digitalEnvelope: {
          encryptedDataBase64: this.encryptedDataBase64,
          macBase64: this.macBase64,
          encryptedSecretKeyConcatsBase64:
              this._getEncryptedSecretKeyConcatsBase64(),
          publicKeysPem: this._getPublicKeysPem()
        }
      });
    }
  };

  /**
   * Container class for the asymmetric encryption of the bit-wise
   * concatenation of the secret key used to symmetrically encrypt the data of
   * a digital envelope and the secret key used to generate an MAC for
   * checking the integrity of the symmetric encryption. The public key used
   * to perform the asymmetric encryption of the secret key concatenation is
   * also included.
   *
   * @class SecretKeyEncryption
   * @param encryptedSecretKeyConcatBase64
   *            {string} the asymmetric encryption of the secret key
   *            concatenation, using the public key provided as input, as a
   *            string in Base64 encoded format.
   * @param publicKeyPem
   *            {string} the public key, of type
   *            {@link java.security.PublicKey}, used to asymmetrically
   *            encrypt the secret key concatenation.
   * @memberof digitalenvelope
   */
  function SecretKeyEncryption(encryptedSecretKeyConcatBase64, publicKeyPem) {
    /**
     * @function getEncryptedSecretKeyConcatenationBase64
     * @return {string} the asymmetrically encrypted secret key
     *         concatenation, as a string in Base64 encoded format.
     */
    var getEncryptedSecretKeyConcatenation = function() {

      return encryptedSecretKeyConcatBase64;
    };

    /**
     * @function getPublicKey
     * @return {string} the public key used to asymmetrically encrypt the
     *         secret key concatenation, as a string in PEM format.
     */
    var getPublicKey = function() {

      return publicKeyPem;
    };

    return {
      getEncryptedSecretKeyConcatenation: getEncryptedSecretKeyConcatenation,
      getPublicKey: getPublicKey
    };
  }

  /**
   * Deserializes a digital envelope.
   *
   * @function deserialize
   * @param serializedDigitalEnvelope
   *            {string} the serialized digital envelope.
   * @return {DigitalEnvelope} the digital envelope after deserialization.
   * @memberof digitalenvelope
   */
  box.digitalenvelope.deserialize = function(serializedDigitalEnvelope) {

    validateDeserializerInput(serializedDigitalEnvelope);

    var digitalEnvelopeJson =
        JSON.parse(serializedDigitalEnvelope).digitalEnvelope;

    var encryptedDataBase64 = digitalEnvelopeJson.encryptedDataBase64;
    var macBase64 = digitalEnvelopeJson.macBase64;
    var encryptedSecretKeyConcatsBase64 =
        digitalEnvelopeJson.encryptedSecretKeyConcatsBase64;
    var publicKeysPem = digitalEnvelopeJson.publicKeysPem;

    var secretKeyEncryptions = [];
    for (var i = 0; i < encryptedSecretKeyConcatsBase64.length; i++) {
      secretKeyEncryptions.push(new SecretKeyEncryption(
          encryptedSecretKeyConcatsBase64[i], publicKeysPem[i]));
    }

    return new DigitalEnvelope(
        encryptedDataBase64, macBase64, secretKeyEncryptions);
  };

  function validateGeneratorInput(dataBase64, publicKeysPem) {
    if (typeof dataBase64 === 'undefined') {
      throw new exceptions.CryptoLibException(
          'Data provided to digital envelope generator is undefined.');
    }

    if (converters.base64Decode(dataBase64) === '') {
      throw new exceptions.CryptoLibException(
          'Data provided to digital envelope generator is empty.');
    }

    if (typeof dataBase64 !== 'string') {
      throw new exceptions.CryptoLibException(
          'Base64 encoded data provided to digital envelope generator is not a String.');
    }

    if (typeof publicKeysPem === 'undefined') {
      throw new exceptions.CryptoLibException(
          'Public key array provided to digital envelope generator is undefined.');
    }

    if (!(publicKeysPem instanceof Array)) {
      throw new exceptions.CryptoLibException(
          'Public key array provided to digital envelope generator is not of type Array.');
    }

    if (publicKeysPem.length === 0) {
      throw new exceptions.CryptoLibException(
          'Public key array provided to digital envelope generator is empty.');
    }
  }

  function validateOpenerInput(digitalEnvelope, privateKeyPem) {
    if (typeof digitalEnvelope === 'undefined') {
      throw new exceptions.CryptoLibException(
          'Digital envelope provided to digital envelope opener is undefined.');
    }

    if (!(digitalEnvelope instanceof Object)) {
      throw new exceptions.CryptoLibException(
          'Digital envelope provided to digital envelope generator is not of type Object.');
    }

    if (typeof privateKeyPem === 'undefined') {
      throw new exceptions.CryptoLibException(
          'Private key provided to digital envelope opener is undefined.');
    }

    if (typeof privateKeyPem !== 'string') {
      throw new exceptions.CryptoLibException(
          'Private key PEM provided to digital envelope opener is not of type String.');
    }
  }

  function validateDeserializerInput(serializedDigitalEnvelope) {
    if (typeof serializedDigitalEnvelope === 'undefined') {
      throw new exceptions.CryptoLibException(
          'Serialized object provided to digital envelope deserializer is undefined.');
    }

    if (typeof serializedDigitalEnvelope !== 'string') {
      throw new exceptions.CryptoLibException(
          'Serialized object provided to digital envelope deserializer is not of type String.');
    }

    if (serializedDigitalEnvelope.length === 0) {
      throw new exceptions.CryptoLibException(
          'Serialized object provided to digital envelope deserializer is empty.');
    }
  }
};

/*
 * 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
 */
cryptolib.modules.digitalenvelope = cryptolib.modules.digitalenvelope || {};

/**
 * Defines the digital envelope service.
 *
 * @namespace digitalenvelope
 */
cryptolib.modules.digitalenvelope.service = function(box) {
  'use strict';

  box.digitalenvelope = box.digitalenvelope || {};
  box.digitalenvelope.service = {};

  var digitalEnvelopeFactory;

  cryptolib('digitalenvelope', function(box) {
    digitalEnvelopeFactory =
        new box.digitalenvelope.factory.DigitalEnvelopeFactory();
  });

  /**
   * Creates a digital envelope, using one or more public keys, each of whose
   * corresponding private key can be used to open the envelope and retrieve
   * the data.
   *
   * @function createDigitalEnvelope
   * @param dataBase64
   *            {string} the data to store in the digital envelope, as a
   *            string in Base64 encoded format.
   * @param publicKeysPem
   *            {string[]} a list of one or more public keys used to generate
   *            the digital envelope, as an array of strings in PEM format.
   * @return {DigitalEnvelope} the newly created digital envelope.
   */
  box.digitalenvelope.service.createDigitalEnvelope = function(
      dataBase64, publicKeysPem) {

    var generator = digitalEnvelopeFactory.getDigitalEnvelopeGenerator();

    return generator.generate(dataBase64, publicKeysPem);
  };

  /**
   * Opens an existing digital envelope and retrieves the data that it
   * contains. The envelope can be opened with any private key corresponding
   * to a public key that was used to generate the envelope.
   *
   * @function openDigitalEnvelope
   * @param digitalEnvelope
   *            {DigitalEnvelope} the digital envelope to open.
   * @param privateKeyPem
   *            {string} the private key used to open the digital envelope, as
   *            a string in PEM format.
   * @return {string} the data contained in the digital envelope, as a string
   *         in Base64 encoded format.
   */
  box.digitalenvelope.service.openDigitalEnvelope = function(
      digitalEnvelope, privateKeyPem) {

    var opener = digitalEnvelopeFactory.getDigitalEnvelopeOpener();

    return opener.open(digitalEnvelope, privateKeyPem);
  };
};
/*
 * 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
 */
cryptolib.modules.homomorphic = cryptolib.modules.homomorphic || {};

cryptolib.modules.homomorphic.cipher = function(box) {
  'use strict';

  box.homomorphic = box.homomorphic || {};
  box.homomorphic.cipher = {};
  /**
   * A submodule that holds ElGamal cipher functionalities.
   *
   * @exports homomorphic/cipher/factory
   */
  box.homomorphic.cipher.factory = {};

  var converters, exceptions, randomFactory, mathematical, keyPairFactory;

  var f = function(box) {
    converters = new box.commons.utils.Converters();
    exceptions = box.commons.exceptions;
    mathematical = box.commons.mathematical;
    randomFactory =
        new box.primitives.securerandom.factory.SecureRandomFactory();
    keyPairFactory = new box.homomorphic.keypair.factory.KeyFactory();
  };

  f.policies = {
    primitives: {
      secureRandom:
          {provider: box.policies.homomorphic.cipher.secureRandom.provider}
    }
  };

  cryptolib('commons', 'primitives.securerandom', 'homomorphic.keypair', f);

  /** @class */
  box.homomorphic.cipher.factory.ElGamalCipherFactory = function() {

  };

  box.homomorphic.cipher.factory.ElGamalCipherFactory.prototype = {

    /**
     * @function createEncrypter
     * @returns ElGamalEncrypter
     */
    createEncrypter: function(elGamalPublicKey, cryptoRandomInteger) {

      if (typeof cryptoRandomInteger === 'undefined') {
        cryptoRandomInteger = randomFactory.getCryptoRandomInteger();
      }

      return new ElGamalEncrypter(elGamalPublicKey, cryptoRandomInteger);
    },

    /**
     * @function createDecrypter
     * @returns ElGamalDecrypter
     */
    createDecrypter: function(elGamalPrivateKey) {
      return new ElGamalDecrypter(elGamalPrivateKey);
    },

    /**
     * @function createRandomDecrypter
     * @returns ElGamalRandomDecrypter
     */
    createRandomDecrypter: function(elGamalPublicKey, cryptoRandomInteger) {
      return new ElGamalRandomDecrypter(elGamalPublicKey, cryptoRandomInteger);
    }
  };

  function validateArray(inputArray) {
    if (typeof inputArray === 'undefined') {
      throw new exceptions.CryptoLibException(
          'The given array should be initialized');
    }

    if (!(inputArray instanceof Array)) {
      throw new exceptions.CryptoLibException(
          'The given array are not from the expected type');
    }

    if (inputArray === []) {
      throw new exceptions.CryptoLibException(
          'The given array cannot be empty');
    }
  }

  /**
   * Defines an ElGamal encrypter.
   * <P>
   * The methods specified in this object allow data to be encrypted using an
   * implementation of the ElGamal cryptosystem.
   *
   * @class ElGamalEncrypter
   * @param {ElGamalPublicKey}
   *            elGamalPublicKey The public key.
   * @param {Object}
   *            cryptoRandomInteger The source of randomness.
   * @returns {ElGamalEncrypter}
   */
  function ElGamalEncrypter(elGamalPublicKey, cryptoRandomInteger) {
    var _elGamalPublicKey = elGamalPublicKey;
    var _cryptoRandomInteger = cryptoRandomInteger;

    /**
     * @function getElGamalPublicKey
     *
     * @returns {ElGamalPublicKey}
     */
    this.getElGamalPublicKey = function() {
      return _elGamalPublicKey;
    };

    /**
     * @function getCryptoRandomInteger
     *
     * @returns {ElGamalPublicKey}
     */
    this.getCryptoRandomInteger = function() {
      return _cryptoRandomInteger;
    };

    // Private methods
    this._validateMessagesNotLargerThanKeySize = function(length) {
      if (_elGamalPublicKey.getGroupElementsArray().length < length) {
        throw new exceptions.CryptoLibException(
            'The list of messages to encrypt was larger than the number of public key elements.');
      }
    };

    this._validatePrePhisNotLargerThanKeySize = function(phis) {
      if (_elGamalPublicKey.getGroupElementsArray().length < phis.length) {
        throw new exceptions.CryptoLibException(
            'The list of prePhis was larger than the number of public key elements.');
      }
    };

    this._preCompute = function(useShortExponent) {

      var gamma;
      var prePhis = [];

      try {
        var publicKeyElements = _elGamalPublicKey.getGroupElementsArray();
        var group = _elGamalPublicKey.getGroup();

        if (useShortExponent &&
           !group.isQuadraticResidueGroup()) {
            throw new exceptions.CryptoLibException(
                'Attempt to ElGamal encrypt using short exponent for Zp subgroup that is not of type quadratic residue.');
        }

        var randomExponent = mathematical.groupUtils.generateRandomExponent(
            group, _cryptoRandomInteger, useShortExponent);

        gamma = group.getGenerator().exponentiate(randomExponent);

        // For each element in the array of public key elements, compute the
        // following: element: prephi[i] = pubKey[i]^(random)
        var pubKeyElementRaised;

        for (var i = 0; i < publicKeyElements.length; i++) {
          pubKeyElementRaised =
              publicKeyElements[i].exponentiate(randomExponent);
          prePhis.push(pubKeyElementRaised);
        }

        return new ElGamalEncrypterValues(randomExponent, gamma, prePhis);
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'There was an error while precomputing values.', error);
      }
    };

    this._compute = function(messages, computationValues) {

      var compressedComputationValues = compressComputationValuesIfNecessary(
          messages.length, computationValues);

      var phis = [];

      // For each element in the array of messages, compute the following:
      // element:phi[i]=message[i]*prePhi[i]
      for (var i = 0; i < compressedComputationValues.getPhis().length; i++) {
        phis.push(
            messages[i].multiply(compressedComputationValues.getPhis()[i]));
      }

      return new box.homomorphic.cipher.ElGamalComputationValues(
          compressedComputationValues.getGamma(), phis);
    };

    this._getListAsQuadraticResidues = function(messages) {
      var valuesZpSubgroupElements = [];

      var group = _elGamalPublicKey.getGroup();
      var groupElement;

      for (var i = 0; i < messages.length; i++) {
        groupElement = new mathematical.ZpGroupElement(
            new box.forge.jsbn.BigInteger(messages[i]), group.getP(),
            group.getQ());
        valuesZpSubgroupElements.push(groupElement);
      }

      return valuesZpSubgroupElements;
    };

    function compressComputationValuesIfNecessary(
        numMessages, computationValues) {
      var phis = computationValues.getPhis();

      if (phis.length <= numMessages) {
        return computationValues;
      }

      var compressedPhiArray =
          mathematical.groupUtils.buildListWithCompressedFinalElement(
              _elGamalPublicKey.getGroup(), phis, numMessages);

      return new box.homomorphic.cipher.ElGamalComputationValues(
          computationValues.getGamma(), compressedPhiArray);
    }
  }

  ElGamalEncrypter.prototype = {

    /**
     * Encrypt the received list of messages (which are represented as
     * ZpGroupElements).
     * <p>
     * The length of the received list of messages must be equal to, or less
     * than, the length of the public key of this encrypter. If this
     * condition is not met, then an exception will be thrown.
     *
     * @function encryptGroupElements
     *
     * @param {Array}
     *            messages an array of ZpGroupElements.
     * @param {Object} [encryptionOption] an optional input parameter. If this option is of type
     *            {boolean}, then the encryption will be pre-computed, using
     *            the value of the boolean to determine whether or not to
     *            use a short random exponent for the pre-computation. If
     *            the option is of type {ElGamalEncrypterValues} then it
     *            will be used as the pre-computation of the encryption.
     *
     * @returns {ElGamalEncrypterValues}
     */
    encryptGroupElements: function(messages, encryptionOption) {

      validateArray(messages);
      this._validateMessagesNotLargerThanKeySize(messages.length);

      var useShortExponent;
      var preComputedValues;
      if (typeof encryptionOption !== 'undefined' &&
          typeof encryptionOption === 'boolean') {
          useShortExponent = encryptionOption;
      } else {
          preComputedValues = encryptionOption;
      }
      
      if (!preComputedValues) {
        preComputedValues = this._preCompute(useShortExponent);
      } else {
        var prePhis = preComputedValues.getPhis();
        validateArray(prePhis);
        this._validatePrePhisNotLargerThanKeySize(prePhis);
      }

      var computationValues = this._compute(
          messages, preComputedValues.getElGamalComputationValues());

      return new ElGamalEncrypterValues(
          preComputedValues.getR(), computationValues.getGamma(),
          computationValues.getPhis());
    },

    /**
     * Pre-compute the ElGamal encrypter values, based on the ElGamal public
     * key provided to the encrypter.
     *
     * @param {boolean}
     *            [useShortExponent] true if a short exponent is to be used
     *            for the pre-computations. Default value is false.
     * @returns {ElGamalEncrypterValues}
     */
    preCompute: function(useShortExponent) {

      return this._preCompute(useShortExponent);
    },

    /**
     * Encrypt the received list of messages (which are represented as
     * Strings).
     * <p>
     * The length of the received list of messages must be equal to, or less
     * than, the length of the public key of this encrypter. If this
     * condition is not met, then an exception will be thrown.
     *
     * @function encryptStrings
     *
     * @param {Array}
     *            messages an array of strings.
     * @param {ElGamalEncrypterValues}
     *            [preComputedValues] an optional parameter. If it is not
     *            provided, then pre-computations are made.
     *
     * @returns {ElGamalEncrypterValues} the pre-computed values.
     */
    encryptStrings: function(messages, preComputedValues) {

      var messagesAsGroupElements = this._getListAsQuadraticResidues(messages);

      if (!preComputedValues) {
        return this.encryptGroupElements(messagesAsGroupElements);
      } else {
        return this.encryptGroupElements(
            messagesAsGroupElements, preComputedValues);
      }
    }
  };

  /**
   * Defines an ElGamal decrypter.
   *
   * @class ElGamalDecrypter
   * @param {ElGamalPrivateKey}
   *            elGamalPrivateKey The ElGamal private key.
   * @returns {ElGamalDecrypter}
   */
  function ElGamalDecrypter(elGamalPrivateKey) {
    function validateCorrectGroup(elGamalPrivateKey) {
      var exponentsArray = elGamalPrivateKey.getExponentsArray();
      var group = elGamalPrivateKey.getGroup();

      for (var i = 0; i < exponentsArray.length; i++) {
        if (!(group.getQ().equals(exponentsArray[i].getQ()))) {
          throw new exceptions.CryptoLibException(
              'Each Exponent must be of the specified group order.');
        }
      }
    }

    validateCorrectGroup(elGamalPrivateKey);

    var _elGamalPrivateKey = elGamalPrivateKey;

    this.getElGamalPrivateKey = function() {
      return _elGamalPrivateKey;
    };

    this._areGroupMembers = function(cipherText) {

      for (var i = 0; i < cipherText.getPhis().length; i++) {
        var next = cipherText.getPhis()[i];
        if (!(_elGamalPrivateKey.getGroup().isGroupMember(next))) {
          return false;
        }
      }

      return true;
    };

    this._validateCiphertextSize = function(cipherText) {
      if (_elGamalPrivateKey.getExponentsArray().length <
          cipherText.getPhis().length) {
        throw new exceptions.CryptoLibException(
            'The list of ciphertext was larger than the number of private key exponents.');
      }
    };

    this._compressKeyIfNecessary = function(numRequired) {

      var exponents = _elGamalPrivateKey.getExponentsArray();
      var group = _elGamalPrivateKey.getGroup();

      if (exponents.length <= numRequired) {
        return _elGamalPrivateKey;
      }

      var listWithCompressedFinalExponent =
          mathematical.groupUtils.buildListWithCompressedFinalExponent(
              group, exponents, numRequired);

      return keyPairFactory.createPrivateKey(
          listWithCompressedFinalExponent, _elGamalPrivateKey.getGroup());
    };
  }

  ElGamalDecrypter.prototype = {

    /**
     * Decrypts a ciphertext.
     * <P>
     * The encrypted message parameter will be a list of group elements,
     * encapsulated within an ComputationValues object.
     * <P>
     * The length of the received ciphertext (number of group elements
     * contained within it) must be equal to, or less than, the length of
     * the private key of this decrypter. If this condition is not met, then
     * an exception will be thrown.
     *
     * @function decrypt
     *
     * @param {Array}
     *            cipherText the encrypted message to be decrypted.
     * @param {Boolean}
     *            [confirmGroupMembership] if true, a confirmation is made that
     * each element in
     *            {@code ciphertext} is a member of the mathematical group
     *            over which this decrypter operates. Default value is false.
     * @returns {Array} the decrypted ciphertext.
     */
    decrypt: function(cipherText, confirmGroupMembership) {

      if (confirmGroupMembership === true &&
          !this._areGroupMembers(cipherText)) {
          throw new exceptions.CryptoLibException(
              'All values to decrypt must be group elements.');
      }

      this._validateCiphertextSize(cipherText);

      var privateKeyAfterCompression =
          this._compressKeyIfNecessary(cipherText.getPhis().length);

      var plaintext = [];
      var negatedExponent;
      var exponentsArray = privateKeyAfterCompression.getExponentsArray();

      for (var i = 0; i < cipherText.getPhis().length; i++) {
        // Compute the e = negate (-) of privKey[i]
        negatedExponent = exponentsArray[i].negate();

        // Compute dm[i]= gamma^(e) * phi[i]
        plaintext.push(cipherText.getGamma()
                           .exponentiate(negatedExponent)
                           .multiply(cipherText.getPhis()[i]));
      }

      return plaintext;
    }
  };

  /**
   * Defines an ElGamal decrypter that can be used to decrypt some ciphertext,
   * given the public key and the source of randomness used to generate the
   * ciphertext.
   *
   * @class ElGamalRandomDecrypter
   * @param {ElGamalPublicKey}
   *            elGamalPublicKey The ElGamal public key.
   * @param {Object} cryptoRandomInteger The source of randomness.
   * @returns {ElGamalDecrypter} The ElGamal decrypter.
   */
  function ElGamalRandomDecrypter(elGamalPublicKey, cryptoRandomInteger) {
    function validateCorrectGroup(elGamalPublicKey) {
      var elementsArray = elGamalPublicKey.getGroupElementsArray();
      var group = elGamalPublicKey.getGroup();

      for (var i = 0; i < elementsArray.length; i++) {
        if (!(group.getP().equals(elementsArray[i].getP()))) {
          throw new exceptions.CryptoLibException(
              'Each element must be of the specified group modulus.');
        }

        if (!(group.getQ().equals(elementsArray[i].getQ()))) {
          throw new exceptions.CryptoLibException(
              'Each element must be of the specified group order.');
        }
      }
    }

    validateCorrectGroup(elGamalPublicKey);

    var _elGamalPublicKey = elGamalPublicKey;

    var _cryptoRandomInteger = cryptoRandomInteger;

    this.getElGamalPublicKey = function() {
      return _elGamalPublicKey;
    };

    this.getCryptoRandomInteger = function() {
      return _cryptoRandomInteger;
    };

    this._areGroupMembers = function(cipherText) {
      for (var i = 0; i < cipherText.getPhis().length; i++) {
        var next = cipherText.getPhis()[i];
        if (!(_elGamalPublicKey.getGroup().isGroupMember(next))) {
          return false;
        }
      }

      return true;
    };

    this._validateCiphertextSize = function(cipherText) {
      if (_elGamalPublicKey.getGroupElementsArray().length <
          cipherText.getPhis().length) {
        throw new exceptions.CryptoLibException(
            'The list of ciphertext was larger than the number of public key elements.');
      }
    };

    this._compressKeyIfNecessary = function(numRequired) {

      var publicKeyElements = _elGamalPublicKey.getGroupElementsArray();
      var group = _elGamalPublicKey.getGroup();

      if (publicKeyElements.length <= numRequired) {
        return _elGamalPublicKey;
      }

      var listWithCompressedFinalElement =
          mathematical.groupUtils.buildListWithCompressedFinalElement(
              group, publicKeyElements, numRequired);

      return keyPairFactory.createPublicKey(
          listWithCompressedFinalElement, _elGamalPublicKey.getGroup());
    };
  }

  ElGamalRandomDecrypter.prototype = {

    /**
     * Decrypts a ciphertext, using the public key and the source of randomness
     * used to generate the ciphertext. <P> The encrypted message parameter will
     * be a list of group elements, encapsulated within an ComputationValues
     * object. <P> The length of the received ciphertext (number of group
     * elements contained within it) must be equal to, or less than, the length
     * of the public key of this decrypter. If this condition is not met, then
     * an exception will be thrown.
     *
     * @function decrypt
     *
     * @param {Array}
     *            cipherText the encrypted message to be decrypted.
     * @param {Boolean}
     *            [confirmGroupMembership] if true, a confirmation is made that
     * each element in
     *            {@code ciphertext} is a member of the mathematical group
     *            over which this decrypter operates. Default value is false.
     * @param {boolean}
     *            [useShortExponent] set to true if a short exponent was used
     * for the encryption. Default value is false.
     * @returns {Array} the decrypted messages.
     */
    decrypt: function(cipherText, confirmGroupMembership, useShortExponent) {

      if (confirmGroupMembership === true &&
          !this._areGroupMembers(cipherText)) {
          throw new exceptions.CryptoLibException(
              'All values to decrypt must be group elements.');
      }

      if (typeof useShortExponent === 'undefined') {
        useShortExponent = false;
      }

      this._validateCiphertextSize(cipherText);

      var publicKeyAfterCompression =
          this._compressKeyIfNecessary(cipherText.getPhis().length);

      var group = publicKeyAfterCompression.getGroup();
      var publicKeyElements = publicKeyAfterCompression.getGroupElementsArray();
      var plaintext = [];
      var negatedExponent;

      var randomExponent = mathematical.groupUtils.generateRandomExponent(
          group, this.getCryptoRandomInteger(), useShortExponent);
      negatedExponent = randomExponent.negate();

      for (var i = 0; i < cipherText.getPhis().length; i++) {
        // Compute dm[i]= publicKeyElement^(-e) * phi[i]
        plaintext.push(publicKeyElements[i]
                           .exponentiate(negatedExponent)
                           .multiply(cipherText.getPhis()[i]));
      }

      return plaintext;
    }
  };

  /**
   * Class which encapsulates an 'r' value (random exponent) and a set of
   * ElGamal encryption values (a gamma value and a list of phi values).
   *
   * @class ElGamalEncrypterValues
   * @param {Exponent}
   *            exponent the Exponent to set.
   * @param {ZpGroupElement}
   *            gamma the gamma (first element) of the ciphertext.
   * @param {Array}
   *            phis the phi values of the ciphertext.
   * @returns {ElGamalEncrypterValues}
   */
  function ElGamalEncrypterValues(exponent, gamma, phis) {
    var _r = exponent;
    var _gamma = gamma;
    var _phis = phis;

    /**
     * @function
     *
     * @returns {Exponent}
     */
    this.getR = function() {
      return _r;
    };

    /**
     * @function
     *
     * @returns {ZpGroupElement}
     */
    this.getGamma = function() {
      return _gamma;
    };

    /**
     * @function
     *
     * @returns {Array}
     */
    this.getPhis = function() {
      return _phis;
    };

    this.getElGamalComputationValues = function() {
      return new box.homomorphic.cipher.ElGamalComputationValues(_gamma, _phis);
    };
  }

  box.homomorphic.cipher.ElGamalEncrypterValues = ElGamalEncrypterValues;

  /**
   * Encapsulates encryption parameters.
   * <p>
   * These parameters should have been generated as defined below:
   * <ul>
   * <li> For p and q:
   * http://csrc.nist.gov/publications/fips/fips186-3/fips_186-3.pdf
   * <li> For g:
   * http://csrc.nist.gov/publications/fips/fips186-3/fips_186-3.pdf
   * <li> For f:
   * http://csrc.nist.gov/publications/fips/fips186-3/fips_186-3.pdf
   * </ul>
   * <p>
   * Note that p, q, and g are received within the mathematical group.
   *
   *
   * @class EncryptionParameters
   * @param {ZpSubgroup}
   *            group the group of the encryption parameters.
   *
   * @returns {EncryptionParameters}
   */
  box.homomorphic.cipher.EncryptionParameters = function(group) {

    var _group = group;

    /**
     * @function
     *
     * @returns {ZpSubgroup}
     */
    this.getEncParamGroup = function() {
      return _group;
    };
  };

  /**
   * Class which encapsulates a gamma and a set of phi values that are Zp
   * subgroup elements.
   *
   * @class ElGamalComputationValues
   * @param {ZpGroupElement}
   *            gamma the gamma (first element) of the ciphertext.
   * @param {Array}
   *            phis the phi values of the ciphertext.
   */
  box.homomorphic.cipher.ElGamalComputationValues = function(gamma, phis) {
    this._gamma = gamma;
    this._phis = phis;
  };

  box.homomorphic.cipher.ElGamalComputationValues.prototype = {

    getGamma: function() {
      return this._gamma;
    },

    getPhis: function() {
      return this._phis;
    },

    stringify: function() {
      var phis = Array();

      for (var i = 0; i < this._phis.length; i++) {
        phis[i] =
            converters.base64FromBigInteger(this._phis[i].getElementValue());
      }

      return JSON.stringify({
        ciphertext: {
          p: converters.base64FromBigInteger(this._gamma.getP()),
          q: converters.base64FromBigInteger(this._gamma.getQ()),
          gamma: converters.base64FromBigInteger(this._gamma.getElementValue()),
          phis: phis
        }
      });
    }
  };

  box.homomorphic.cipher.deserializeElGamalComputationValues = function(
      serializedObject) {

    var valuesJson = JSON.parse(serializedObject).ciphertext;

    var p = converters.base64ToBigInteger(valuesJson.p);
    var q = converters.base64ToBigInteger(valuesJson.q);

    var gamma = new mathematical.ZpGroupElement(
        converters.base64ToBigInteger(valuesJson.gamma), p, q);

    var phis = [];
    for (var i = 0; i < valuesJson.phis.length; i++) {
      phis.push(new mathematical.ZpGroupElement(
          converters.base64ToBigInteger(valuesJson.phis[i]), p, q));
    }

    return new box.homomorphic.cipher.ElGamalComputationValues(gamma, phis);
  };
};

/*
 * 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
 */
cryptolib.modules.homomorphic = cryptolib.modules.homomorphic || {};

cryptolib.modules.homomorphic.keypair = function(box) {
  'use strict';

  box.homomorphic = box.homomorphic || {};
  box.homomorphic.keypair = {};

  /**
   * A submodule that holds ElGamal key pair functionalities.
   * @exports homomorphic/keypair/factory
   */
  box.homomorphic.keypair.factory = {};

  var converters;
  var mathematical;
  var f = function(box) {
    converters = new box.commons.utils.Converters();
    mathematical = box.commons.mathematical;
  };

  cryptolib('commons.exceptions', 'commons.utils', 'commons.mathematical', f);

  /** @class */
  box.homomorphic.keypair.factory.KeyFactory = function() {

  };

  box.homomorphic.keypair.factory.KeyFactory.prototype = {

    /**
     * @function
     * @returns ElGamalKeyPair
     */
    createKeyPair: function(elGamalPublicKey, elGamalPrivateKey) {
      return new ElGamalKeyPair(elGamalPublicKey, elGamalPrivateKey);
    },

    /**
     * @function
     * @returns ElGamalPublicKey
     */
    createPublicKey: function(
        serializedElGamalPublicKeyB64OrListOfGroupElements, keyGroup) {
      return new ElGamalPublicKey(
          serializedElGamalPublicKeyB64OrListOfGroupElements, keyGroup);
    },

    /**
     * @function
     * @returns ElGamalPrivateKey
     */
    createPrivateKey: function(
        serializedElGamalPrivateKeyB64OrListOfExponents, keyGroup) {
      return new ElGamalPrivateKey(
          serializedElGamalPrivateKeyB64OrListOfExponents, keyGroup);
    }
  };

  /**
   * Representation of an ElGamal key pair. This key pair is composed of a
   * private key and a public key.
   * @class ElGamalKeyPair
   * @param elGamalPublicKey
   *            ElGamal public key.
   * @param elGamalPrivateKey
   *            ElGamal private key.
   * @returns {ElGamalKeyPair}
   */
  function ElGamalKeyPair(elGamalPublicKey, elGamalPrivateKey) {
    var _elGamalPublicKey = elGamalPublicKey;
    var _elGamalPrivateKey = elGamalPrivateKey;

    this.getPublicKey = function() {
      return _elGamalPublicKey;
    };

    this.getPrivateKey = function() {
      return _elGamalPrivateKey;
    };
  }

  /**
   * Encapsulates an ElGamal public key.
   * <P>
   * This class includes the key itself (which is actually a list of
   * elements), as well as the mathematical group that this key belongs to.
   * @class ElGamalPublicKey
   * @param {String_or_Array} serializedElGamalPublicKeyB64OrListOfGroupElements
   *            this parameter can be either a string containing the
   *     serialized ElGamal public key to JSON string, or an array of
   * ZpGroupElement.
   * @param {ZpSubgroup} keyGroup
   *            the ZpSubgroup of the public key.
   * @returns {ElGamalPublicKey}
   */
  function ElGamalPublicKey(
      serializedElGamalPublicKeyB64OrListOfGroupElements, keyGroup) {
    var _group;
    var _groupElementsArray = [];

    var _fromJson = function(serializedElGamalPublicKey) {

      var parsedPublicKey = JSON.parse(serializedElGamalPublicKey).publicKey;

      var p = converters.base64ToBigInteger(
          parsedPublicKey.zpSubgroup.p.toString());
      var q = converters.base64ToBigInteger(
          parsedPublicKey.zpSubgroup.q.toString());
      var generator = converters.base64ToBigInteger(
          parsedPublicKey.zpSubgroup.g.toString());
      _group = new mathematical.ZpSubgroup(generator, p, q);

      for (var i = 0; i < parsedPublicKey.elements.length; i++) {
        var elementValue =
            converters.base64ToBigInteger(parsedPublicKey.elements[i]);
        var element = new mathematical.ZpGroupElement(
            elementValue, _group.getP(), _group.getQ());
        _groupElementsArray.push(element);
      }
    };

    if (typeof keyGroup === 'undefined') {
      var serializedElGamalPublicKey = converters.base64Decode(
          serializedElGamalPublicKeyB64OrListOfGroupElements);

      _fromJson(serializedElGamalPublicKey);
    } else {
      _groupElementsArray = serializedElGamalPublicKeyB64OrListOfGroupElements;
      _group = keyGroup;
    }


    this.getGroup = function() {
      return _group;
    };

    this.getGroupElementsArray = function() {
      return _groupElementsArray;
    };
  }

  /**
   * Encapsulates an ElGamal private key.
   * <P>
   * This class includes the key itself (which is actually a list of
   * exponents), as well as the mathematical group that this key belongs to.
   * @class ElGamalPrivateKey
   * @param {String_or_Array} serializedElGamalPrivateKeyB64OrListOfExponents
   *            this parameter can be either a string containing the
   *            serialized to JSON string ElGamal private key
   *            or an array of Exponents.
   * @param {ZpSubgroup} keyGroup
   *            the ZpSubgroup of the private key.
   * @returns {ElGamalPrivateKey}
   */
  function ElGamalPrivateKey(
      serializedElGamalPrivateKeyB64OrListOfExponents, keyGroup) {
    var _group;
    var _exponentsArray = [];

    var _fromJson = function(serializedElGamalPrivateKey) {

      var parsedPrivateKey = JSON.parse(serializedElGamalPrivateKey).privateKey;

      var p = converters.base64ToBigInteger(
          parsedPrivateKey.zpSubgroup.p.toString());
      var q = converters.base64ToBigInteger(
          parsedPrivateKey.zpSubgroup.q.toString());
      var generator = converters.base64ToBigInteger(
          parsedPrivateKey.zpSubgroup.g.toString());
      _group = new mathematical.ZpSubgroup(generator, p, q);

      for (var i = 0; i < parsedPrivateKey.exponents.length; i++) {
        var exponentValue =
            converters.base64ToBigInteger(parsedPrivateKey.exponents[i]);
        var exponent = new mathematical.Exponent(_group.getQ(), exponentValue);
        _exponentsArray.push(exponent);
      }
    };

    if (typeof keyGroup === 'undefined') {
      var serializedElGamalPrivateKey = converters.base64Decode(
          serializedElGamalPrivateKeyB64OrListOfExponents);
      _fromJson(serializedElGamalPrivateKey);
    } else {
      _exponentsArray = serializedElGamalPrivateKeyB64OrListOfExponents;
      _group = keyGroup;
    }

    this.getGroup = function() {
      return _group;
    };

    this.getExponentsArray = function() {
      return _exponentsArray;
    };
  }
};

/*
 * 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
 */
cryptolib.modules.homomorphic = cryptolib.modules.homomorphic || {};

cryptolib.modules.homomorphic.service = function(box) {
  'use strict';

  box.homomorphic = box.homomorphic || {};
  /**
   * A module that provides high-level ElGamal cryptographic services.
   *
   * @exports homomorphic/service
   */
  box.homomorphic.service = {};

  var elGamalFactory;
  var keyFactory;

  cryptolib('homomorphic.cipher', 'homomorphic.keypair', function(box) {

    elGamalFactory = new box.homomorphic.cipher.factory.ElGamalCipherFactory();
    keyFactory = new box.homomorphic.keypair.factory.KeyFactory();
  });

  /**
   * @function createElGamalKeyPair
   * @param elGamalPublicKey
   *            {Object} an ElGamal public key.
   * @param elGamalPrivateKey
   *            {Object} an ElGamal private key.
   *
   * @returns {Object} an object of ElGamalKeyPair.
   */
  box.homomorphic.service.createElGamalKeyPair = function(
      elGamalPublicKey, elGamalPrivateKey) {
    return keyFactory.createKeyPair(elGamalPublicKey, elGamalPrivateKey);
  };

  /**
   * @function createElGamalPublicKey
   * @param serializedElGamalPublicKeyB64
   *            {String] a serialized ElGamalPublicKey, in base 64 encoded
   *            format.
   *
   * @returns {Object} a ElGamalPublicKey, as an object.
   */
  box.homomorphic.service.createElGamalPublicKey = function(
      serializedElGamalPublicKeyB64) {
    return keyFactory.createPublicKey(serializedElGamalPublicKeyB64);
  };

  /**
   * @function createElGamalPrivateKey
   * @param serializedElGamalPrivateKeyB64
   *            {String} a serialized ElGamalPrivateKey, in base 64 encoded
   *            format.
   *
   * @returns {Object} a ElGamalPrivateKey, as an object.
   */
  box.homomorphic.service.createElGamalPrivateKey = function(
      serializedElGamalPrivateKeyB64) {
    return keyFactory.createPrivateKey(serializedElGamalPrivateKeyB64);
  };

  /**
   * @function createEncrypter
   * @param elGamalPublicKey
   *            {Object} a public key, as an ElGamalPublicKey object.
   * @param [cryptoRandomInteger] {Object} an optional parameter to set the
   * source of randomness for the generation of the ElGamal encryption
   * exponent. If not provided, the source will be created internally.
   *
   * @returns {Object} an ElGamalEncrypter.
   */
  box.homomorphic.service.createEncrypter = function(
      elGamalPublicKey, cryptoRandomInteger) {
    return elGamalFactory.createEncrypter(
        elGamalPublicKey, cryptoRandomInteger);
  };

  /**
   * @function createDecrypter
   * @param elGamalPrivateKey
   *            {Object} a private key, as an ElGamalPrivateKey object.
   *
   * @returns {Object} an ElGamalDecrypter.
   */
  box.homomorphic.service.createDecrypter = function(elGamalPrivateKey) {
    return elGamalFactory.createDecrypter(elGamalPrivateKey);
  };

  /**
   * @function createRandomDecrypter
   * @param elGamalPublicKey
   *            {Object} a public key, as an ElGamalPublicKey object.
   * @param cryptoRandomInteger {Object} the
   * source of randomness for the generation of the ElGamal encryption
   * exponent.
   * @returns {Object} an ElGamalRandomDecrypter.
   */
  box.homomorphic.service.createRandomDecrypter = function(
      elGamalPublicKey, cryptoRandomInteger) {
    return elGamalFactory.createRandomDecrypter(
        elGamalPublicKey, cryptoRandomInteger);
  };
};

/*
 * 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
 */
cryptolib.modules.primitives = cryptolib.modules.primitives || {};

/** @namespace primitives/derivation */
cryptolib.modules.primitives.derivation = function(box) {
  'use strict';

  box.primitives = box.primitives || {};
  box.primitives.derivation = {};
  /**
   * A module that holds derivation functionalities.
   *
   * @exports primitives/derivation
   */
  box.primitives.derivation.factory = {};

  var policies = {
    pbkdf: {
      provider: box.policies.primitives.derivation.pbkdf.provider,
      hash: box.policies.primitives.derivation.pbkdf.hash,
      saltLengthBytes: box.policies.primitives.derivation.pbkdf.saltLengthBytes,
      keyLengthBytes: box.policies.primitives.derivation.pbkdf.keyLengthBytes,
      iterations: box.policies.primitives.derivation.pbkdf.iterations
    }
  };

  var exceptions, converters;
  cryptolib('rnd', 'commons', function(box) {
    exceptions = box.commons.exceptions;
    converters = new box.commons.utils.Converters();
  });

  /**
   * A factory class for generating {@link CryptoForgePBKDFSecretKeyGenerator}
   * objects.
   *
   * @class
   */
  box.primitives.derivation.factory.SecretKeyGeneratorFactory = function() {

  };

  box.primitives.derivation.factory.SecretKeyGeneratorFactory.prototype = {
    /**
     * Creates a generator for deriving passwords.
     *
     * @function
     * @returns {primitives/derivation.CryptoForgePBKDFSecretKeyGenerator} a
     *          {@link primitives/derivation.CryptoForgePBKDFSecretKeyGenerator}
     *          object.
     */
    createPBKDF: function() {
      var provider = policies.pbkdf.provider;

      try {
        if (provider === Config.primitives.derivation.pbkdf.provider.FORGE) {
          return new CryptoForgePBKDFSecretKeyGenerator();
        } else {
          throw new exceptions.CryptoLibException(
              'The specified provider ' + provider + ' is not supported.');
        }
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'A CryptoPBKDFSecretKeyGenerator object could not be created.',
            error);
      }
    }
  };

  /**
   *
   * @class
   * @memberof primitives/derivation
   */
  function CryptoForgePBKDFSecretKeyGenerator() {
    validateHash(policies.pbkdf.hash);
    validateKeyLengthBytes(policies.pbkdf.keyLengthBytes);
    validateSaltLengthBytes(policies.pbkdf.saltLengthBytes);
    validateIterations(policies.pbkdf.iterations);

    this.keyLengthBytes = policies.pbkdf.keyLengthBytes;
  }

  CryptoForgePBKDFSecretKeyGenerator.prototype = {

    /**
     * @param {string}
     *            password the password, as a string of bytes in Base64
     *            encoded format.
     * @param {string}
     *            salt the salt, as a string of bytes in Base64 encoded
     *            format. Note that the salt length should coincide with the
     *            one required by the hash algorithm.
     *
     * @return {string} the derived key, as a string of bytes in Base64
     *         encoded format.
     */
    generateSecret: function(passwordB64, saltB64) {

      var password = converters.base64Decode(passwordB64);

      var salt = converters.base64Decode(saltB64);

      if (salt.length < policies.pbkdf.saltLengthBytes) {
        throw new exceptions.CryptoLibException(
            'The salt byte length ' + salt.length +
            ' is less than the minimum allowed salt length ' +
            policies.pbkdf.saltLengthBytes +
            ' set by the cryptographic policy.');
      }

      var derivedKey = sjcl.misc.pbkdf2(
          password, sjcl.codec.base64.toBits(saltB64),
          policies.pbkdf.iterations, this.keyLengthBytes * 8,
          hashCallback(policies.pbkdf.hash));

      return sjcl.codec.base64.fromBits(derivedKey);
    }
  };

  function hashCallback(algorithm) {
    if (algorithm === Config.primitives.derivation.pbkdf.hash.SHA256) {
      return null;
    } else {
      throw new exceptions.CryptoLibException(
          'Hash algorithm \'' + algorithm + '\' is unsupported');
    }
  }

  function validateHash(hash) {
    var supportedHashAlgorithms =
        [Config.primitives.derivation.pbkdf.hash.SHA256];

    if (supportedHashAlgorithms.indexOf(hash) < 0) {
      throw new exceptions.CryptoLibException(
          'The specified hash algorithm ' + hash +
          ' is not one of the supported options: ' + supportedHashAlgorithms);
    }
  }

  function validateSaltLengthBytes(saltLengthBytes) {
    var supportedSaltLengthBytes = [
      Config.primitives.derivation.pbkdf.saltLengthBytes.SL_20,
      Config.primitives.derivation.pbkdf.saltLengthBytes.SL_32
    ];

    if (typeof saltLengthBytes !== 'number' || saltLengthBytes % 1 !== 0 ||
        saltLengthBytes < 0) {
      throw new exceptions.CryptoLibException(
          'The specified salt byte length ' + saltLengthBytes +
          ' is not a positive integer.');
    }

    if (supportedSaltLengthBytes.indexOf(saltLengthBytes) < 0) {
      throw new exceptions.CryptoLibException(
          'The specified salt byte length ' + saltLengthBytes +
          ' is not one of the supported options: ' + supportedSaltLengthBytes);
    }
  }

  function validateKeyLengthBytes(keyLengthBytes) {
    var supportedKeyLengthBytes = [
      Config.primitives.derivation.pbkdf.keyLengthBytes.KL_16,
      Config.primitives.derivation.pbkdf.keyLengthBytes.KL_32
    ];

    if (typeof keyLengthBytes !== 'number' || keyLengthBytes % 1 !== 0 ||
        keyLengthBytes < 0) {
      throw new exceptions.CryptoLibException(
          'The specified key byte length ' + keyLengthBytes +
          ' is not a positive number.');
    }

    if (supportedKeyLengthBytes.indexOf(keyLengthBytes) < 0) {
      throw new exceptions.CryptoLibException(
          'The specified key byte length ' + keyLengthBytes +
          ' is not one of the supported options: ' + supportedKeyLengthBytes);
    }
  }

  function validateIterations(iterations) {
    var supportedIterations = [
      Config.primitives.derivation.pbkdf.iterations.I_1,
      Config.primitives.derivation.pbkdf.iterations.I_8000,
      Config.primitives.derivation.pbkdf.iterations.I_16000,
      Config.primitives.derivation.pbkdf.iterations.I_32000,
      Config.primitives.derivation.pbkdf.iterations.I_64000
    ];

    if (typeof iterations !== 'number' || iterations % 1 !== 0 ||
        iterations < 0) {
      throw new exceptions.CryptoLibException(
          'The specified number of iterations ' + iterations +
          ' is not a positive integer.');
    }

    if (supportedIterations.indexOf(iterations) < 0) {
      throw new exceptions.CryptoLibException(
          'The specified number of iterations ' + iterations +
          ' is not one of the supported options: ' + supportedIterations);
    }
  }
};

/*
 * 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
 */
cryptolib.modules.primitives = cryptolib.modules.primitives || {};

/**
 * @namespace primitives/messagedigest
 */
cryptolib.modules.primitives.messagedigest = function(box) {
  'use strict';

  box.primitives = box.primitives || {};
  box.primitives.messagedigest = box.primitives.messagedigest || {};

  /**
   * A module that holds message digest functionalities.
   * @exports primitives/messagedigest
   */
  box.primitives.messagedigest.factory =
      box.primitives.messagedigest.factory || {};

  var policies = {
    messagedigest: {
      algorithm: box.policies.primitives.messagedigest.algorithm,
      provider: box.policies.primitives.messagedigest.provider
    }
  };

  var converters, exceptions;

  cryptolib('commons', function(box) {
    converters = new box.commons.utils.Converters();
    exceptions = box.commons.exceptions;
  });

  /**
   * A factory class for creating a message digest generator and verifier.
   * @class
   */
  box.primitives.messagedigest.factory.MessageDigestFactory = function() {};

  box.primitives.messagedigest.factory.MessageDigestFactory.prototype = {
    /**
     * Gets a message dister object.
     * @function
     * @returns {primitives/messagedigest.CryptoForgeMessageDigest}
     */
    getCryptoMessageDigest: function() {

      try {
        if (policies.messagedigest.provider ===
            Config.primitives.messagedigest.provider.FORGE) {
          return this.getCryptoForgeMessageDigest();
        } else {
          throw new exceptions.CryptoLibException(
              'No suitable provider was provided.');
        }
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'A CryptoSymmetricCipher could not be obtained.', error);
      }
    },

    getCryptoForgeMessageDigest: function() {
      return new CryptoForgeMessageDigest();
    }
  };

  /**
   * Defines a message digest hash function.
   * @class
   * @memberof primitives/messagedigest
   */
  function CryptoForgeMessageDigest() {
    this.digester = null;
    try {
      if (policies.messagedigest.algorithm ===
          Config.primitives.messagedigest.algorithm.SHA256) {
        this.digester = forge.md.sha256.create();
      } else if (
          policies.messagedigest.algorithm ===
          Config.primitives.messagedigest.algorithm.SHA512_224) {
        this.digester = forge.md.sha512.sha224.create();
      } else {
        var errorMessage = 'Message digester type \'' +
            policies.messagedigest.algorithm +
            '\' not recognized by CryptoForgeMessageDigest.';
        throw new exceptions.CryptoLibException(errorMessage);
      }
    } catch (error) {
      throw new exceptions.CryptoLibException(
          'CryptoForgeMessageDigest could not be created.', error);
    }
  }

  CryptoForgeMessageDigest.prototype = {

    /**
     * Initializes the message digest generator.
     * @function
     */
    start: function() {
      try {
        this.digester.start();
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'Message digest could not be initialized.', error);
      }
    },

    /**
     * Updates the message digest generator.
     * @function
     * @param {Array} arrayDataBase64
     *            data to compute the hash, as an array of string in Base64
     *            encoded format.
     */
    update: function(arrayDataBase64) {

      try {
        if (!arrayDataBase64 || arrayDataBase64.length < 1) {
          throw new exceptions.CryptoLibException(
              'The array of data in Base64 should contain at least one element.');
        }

        for (var i = 0; i < arrayDataBase64.length; i++) {
          this.digester.update(converters.base64Decode(arrayDataBase64[i]));
        }

      } catch (error) {
        throw new exceptions.CryptoLibException(
            'Message digest could not be updated.', error);
      }
    },

    /**
     * Generates the message digest for the updated data.
     * @function
     * @returns {string} Message digest, as string in Base64 encoded format.
     */
    digest: function() {

      try {
        var messageDigest = this.digester.digest();

        var rawOutput = messageDigest.getBytes();

        var encodedOutput = converters.base64Encode(rawOutput);

        return encodedOutput;
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'Message digest could not be generated.', error);
      }
    }
  };
};

/*
 * 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
 */
cryptolib.modules.primitives = cryptolib.modules.primitives || {};

/**
 * @namespace primitives/securerandom
 */
cryptolib.modules.primitives.securerandom = function(box) {
  'use strict';

  box.primitives = box.primitives || {};
  box.primitives.securerandom = {};
  /**
   * A module that holds secure random functionalities.
   *
   * @exports primitives/securerandom
   */
  box.primitives.securerandom.factory = {};

  var policies = {
    secureRandom: {provider: box.policies.primitives.secureRandom.provider}
  };

  var converters, exceptions;

  cryptolib('commons', function(box) {
    converters = new box.commons.utils.Converters();
    exceptions = box.commons.exceptions;
  });

  /**
   * A factory class for creating random generators.
   *
   * @class
   */
  box.primitives.securerandom.factory.SecureRandomFactory = function() {

  };

  box.primitives.securerandom.factory.SecureRandomFactory.prototype = {
    /**
     * Instantiates a random byte generator.
     *
     * @function
     * @returns {primitives/securerandom.CryptoScytlRandomBytes}
     */
    getCryptoRandomBytes: function() {

      try {
        if (policies.secureRandom.provider ===
            Config.primitives.secureRandom.provider.SCYTL) {
          return this.getCryptoScytlRandomBytes();
        } else {
          throw new exceptions.CryptoLibException(
              'No suitable provider was provided.');
        }
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'A CryptoRandomBytes could not be obtained.', error);
      }

    },

    /**
     * Instantiates a Scytl random byte generator.
     *
     * @function
     * @returns {primitives/securerandom.CryptoScytlRandomBytes}
     */
    getCryptoScytlRandomBytes: function() {
      return new CryptoScytlRandomBytes();
    },

    /**
     * Instantiates a random integer generator.
     *
     * @function
     * @returns {primitives/securerandom.CryptoScytlRandomBytes}
     */
    getCryptoRandomInteger: function() {

      try {
        if (policies.secureRandom.provider ===
            Config.primitives.secureRandom.provider.SCYTL) {
          return this.getCryptoScytlRandomInteger();
        } else {
          throw new exceptions.CryptoLibException(
              'No suitable provider was provided.');
        }
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'A CryptoRandomBytes could not be obtained.', error);
      }

    },

    /**
     * Instantiates a Scytl random integer generator.
     *
     * @function
     * @returns {primitives/securerandom.CryptoScytlRandomBytes}
     */
    getCryptoScytlRandomInteger: function() {
      return new CryptoScytlRandomInteger();
    }
  };

  var validate = function(length) {
    try {
      if (typeof length !== 'number') {
        throw new exceptions.CryptoLibException(
            'The given length is not a number');
      }
      if (length < 1) {
        throw new exceptions.CryptoLibException(
            'The given length should be a strictly positive integer.');
      }
      if (length > box.MAXIMUM_LENGTH_TO_GENERATE_DATA) {
        throw new exceptions.CryptoLibException(
            'The given length should be lower than or equal to ' +
            box.MAXIMUM_LENGTH_TO_GENERATE_DATA);
      }
    } catch (error) {
      throw new exceptions.CryptoLibException(
          'Random generation failed.', error);
    }
  };

  /**
   * The SCYTL Bytes Random Generator
   *
   * @class
   * @memberof primitives/securerandom
   */
  function CryptoScytlRandomBytes() {}

  CryptoScytlRandomBytes.prototype = {

    /**
     * Generates secure random bytes.
     *
     * @function
     * @param {number}
     *            numBytes number of bytes to be generated..
     * @returns {string} secure random string.
     */
    nextRandom: function(length) {

      validate(length);

      var randomBytes = box.prng.generate(length);

      return randomBytes.toString();
    },

    /**
     * Method to be compliant with the Forge library.
     *
     * @function
     * @param {number}
     *            numBytes number of bytes to be generated..
     * @returns {string} secure random string.
     */
    getBytesSync: function(length) {
      return this.nextRandom(length);
    }
  };

  /**
   * The SCYTL Integer Random Generator
   *
   * @class
   * @memberof primitives/securerandom
   */
  function CryptoScytlRandomInteger() {}

  CryptoScytlRandomInteger.prototype = {

    /**
     * Generates a secure random big integer (Forge).
     *
     * @function
     * @params {number} length the bit length of the random integer.
     * @returns {object} secure random big integer.
     */
    nextRandom: function(length) {

      validate(length);

      var n = ((new box.forge.jsbn.BigInteger('10')).pow(length))
                  .subtract(box.forge.jsbn.BigInteger.ONE);

      var bitLength = n.bitLength();

      var numberOfBytes = bitLength / box.NUMBER_OF_BITS_PER_BYTE;
      numberOfBytes = Math.floor(numberOfBytes);

      var modBytes = bitLength % box.NUMBER_OF_BITS_PER_BYTE;

      if (modBytes !== 0) {
        numberOfBytes++;
      }

      var generatedInteger;

      do {
        var randomBytes = box.prng.generate(numberOfBytes);
        randomBytes = '\x00' + randomBytes;
        randomBytes = converters.bytesFromString(randomBytes);
        generatedInteger = new box.forge.jsbn.BigInteger(randomBytes);
      } while ((generatedInteger.compareTo(forge.jsbn.BigInteger.ZERO) <= 0) ||
               (generatedInteger.compareTo(n) > 0));

      return generatedInteger;
    },

    /**
     * Generates a secure random big integer according to the specified bit
     * length.
     *
     * @function
     * @params {number} bitsLength the bit length of the random integer.
     * @returns {object} Secure random big integer.
     */
    nextRandomByBits: function(bitsLength) {

      var byteLength = Math.ceil(bitsLength / box.NUMBER_OF_BITS_PER_BYTE);

      var modBytes = box.NUMBER_OF_BITS_PER_BYTE -
          (bitsLength % box.NUMBER_OF_BITS_PER_BYTE);

      var randomBytes = box.prng.generate(byteLength);

      randomBytes = '\x00' + randomBytes;
      randomBytes = converters.bytesFromString(randomBytes);
      var generatedInteger = new box.forge.jsbn.BigInteger(randomBytes);

      generatedInteger = generatedInteger.shiftRight(modBytes);

      return generatedInteger;
    }
  };
};

/*
 * 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
 */
cryptolib.modules.primitives = cryptolib.modules.primitives || {};

/**
 * Defines primitives services.
 * @namespace primitives/service
 */
cryptolib.modules.primitives.service = function(box) {
  'use strict';

  box.primitives = box.primitives || {};
  /**
   * A module that holds primitives API.
   * @exports primitives/service
   */
  box.primitives.service = {};

  var securerandomFactory, messageDigestFactory, secretKeyGeneratorFactory;

  cryptolib(
      'primitives.securerandom', 'commons.exceptions',
      'primitives.messagedigest', 'primitives.derivation', function(box) {
        securerandomFactory =
            new box.primitives.securerandom.factory.SecureRandomFactory();
        messageDigestFactory =
            new box.primitives.messagedigest.factory.MessageDigestFactory();
        secretKeyGeneratorFactory =
            new box.primitives.derivation.factory.SecretKeyGeneratorFactory();
      });

  /**
   * Provides a random bytes generator.
   * @function
   * @returns {primitives/securerandom.CryptoScytlRandomBytes} a CryptoRandomBytes object.
   */
  box.primitives.service.getCryptoRandomBytes = function() {
    return securerandomFactory.getCryptoRandomBytes();
  };

  /**
   * Provides a random integer generator.
   * @function
   * @returns {primitives/securerandom.CryptoScytlRandomInteger} a CryptoRandomInteger object.
   */
  box.primitives.service.getCryptoRandomInteger = function() {
    return securerandomFactory.getCryptoRandomInteger();
  };

  /**
   * Generates a hash for the given data.
   * @function
   * @parameter {array} ArrayDataBase64 the input data for the hash.
   * @returns {string} the hash, as a string in Base64 encoded format.
   */
  box.primitives.service.getHash = function(ArrayDataBase64) {
    var hashFunction = messageDigestFactory.getCryptoMessageDigest();
    hashFunction.start();
    hashFunction.update(ArrayDataBase64);
    return hashFunction.digest();
  };

  /**
   * Provides a derived secret key generator.
   * @function
   * @returns {primitives/derivation.CryptoForgePBKDFSecretKeyGenerator} a the pbkdf Generator.
   */
  box.primitives.service.getPbkdfSecretKeyGenerator = function() {
    return secretKeyGeneratorFactory.createPBKDF();
  };
};

/*
 * 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
 */
cryptolib.modules = cryptolib.modules || {};
cryptolib.modules.proofs = cryptolib.modules.proofs || {};

/**
 * Defines Zero-Knowledge Proofs (ZKPs).
 */
cryptolib.modules.proofs.implementation = function(box) {
  'use strict';

  box.proofs = box.proofs || {};

  var exceptions, mathematical, utils, converters, secureRandomFactory,
      messageDigestFactory;

  var f = function(box) {
    exceptions = box.commons.exceptions;
    mathematical = box.commons.mathematical;
    utils = box.commons.utils;
    converters = new box.commons.utils.Converters();
    secureRandomFactory =
        new box.primitives.securerandom.factory.SecureRandomFactory();
    messageDigestFactory =
        new box.primitives.messagedigest.factory.MessageDigestFactory();
  };

  f.policies = {
    primitives: {
      messagedigest: {
        algorithm: box.policies.proofs.messagedigest.algorithm,
        provider: box.policies.proofs.messagedigest.provider
      },
      secureRandom: {provider: box.policies.proofs.secureRandom.provider}
    }
  };

  cryptolib(
      'commons.exceptions', 'commons.mathematical', 'primitives.messagedigest',
      'commons.utils', 'primitives.securerandom', f);

  /**
   * Schnorr proof generator.
   */
  box.proofs.SchnorrProofGenerator = function(voterID, electionID, group) {

    checkNotEmpty('voter ID', voterID);
    checkNotEmpty('election ID', electionID);
    checkDefined('group', group);

    var computationRules = [[[1, 1]]];
    var _voterID = voterID;
    var _electionID = electionID;
    var _progressMeter;
    var _numOutputs = 1;

    var buildListOfBaseElements = function() {

      var baseElements = [];
      baseElements.push(group.getGenerator());
      return baseElements;
    };

    var phiFunctionSchnorr = new box.proofs.PhiFunction(
        group, 1, _numOutputs, buildListOfBaseElements(), computationRules);
    var _prover = new box.proofs.Prover(group, phiFunctionSchnorr);

    this._buildListPublicValues = function(exponentiatedElement) {
      var publicList = [];
      publicList.push(exponentiatedElement);
      return publicList;
    };

    this._getProver = function() {
      return _prover;
    };

    this._buildListExponents = function(witness) {

      var exponentList = [];
      exponentList.push(witness);

      return exponentList;
    };

    this._buildDataString = function() {

      return 'SchnorrProof:VoterID=' + _voterID +
          'ElectionEventID=' + _electionID;
    };

    this._getProgressMeter = function() {
      return _progressMeter;
    };

    this.initProgressMeter = function(
        progressCallback, progressPercentMinCheckInterval) {
      _progressMeter = createProgressMeter(
          _numOutputs, progressCallback, progressPercentMinCheckInterval);
      return this;
    };
  };

  box.proofs.SchnorrProofGenerator.prototype = {

    preCompute: function() {
      return this._getProver().preCompute(1, this._getProgressMeter());
    },

    generate: function(exponentiatedElement, witness, preComputedValues) {
      checkDefined('exponentiated element', exponentiatedElement);
      checkDefined('witness', witness);

      if (!preComputedValues) {
        preComputedValues = this.preCompute();
      }

      return this._getProver().prove(
          this._buildListPublicValues(exponentiatedElement),
          this._buildListExponents(witness), this._buildDataString(),
          preComputedValues);
    }
  };

  /**
   * Exponentiation proof generator.
   */
  box.proofs.ExponentiationProofGenerator = function(baseElements, group) {

    checkDefined('base elements', baseElements);
    checkDefined('group', group);

    var buildComputationRules = function(k) {

      var rules = [];

      for (var i = 0; i < k; i++) {
        rules[i] = [];
        rules[i][0] = [];
        rules[i][0].push(i + 1);
        rules[i][0].push(1);
      }

      return rules;
    };

    var _numOutputs = baseElements.length;
    var phiFunctionExponentiation = new box.proofs.PhiFunction(
        group, 1, _numOutputs, baseElements,
        buildComputationRules(_numOutputs));

    var _prover = new box.proofs.Prover(group, phiFunctionExponentiation);

    var _progressMeter;

    this._getProgressMeter = function() {
      return _progressMeter;
    };

    this.initProgressMeter = function(
        progressCallback, progressPercentMinCheckInterval) {
      _progressMeter = createProgressMeter(
          _numOutputs, progressCallback, progressPercentMinCheckInterval);
      return this;
    };

    this.check = function(exponentiatedElements) {
      if (exponentiatedElements.length !== _numOutputs) {
        throw new exceptions.CryptoLibException(
            'The number of exponentiated elements must be equal to the number of base elements');
      }
    };

    this._getProver = function() {
      return _prover;
    };
  };

  box.proofs.ExponentiationProofGenerator.prototype = {

    preCompute: function() {
      var EXPONENT_QUANTITY = 1;
      return this._getProver().preCompute(
          EXPONENT_QUANTITY, this._getProgressMeter());
    },

    generate: function(exponentiatedElements, exponent, preComputedValues) {

      checkDefined('exponentiatedElements', exponentiatedElements);
      checkDefined('exponent', exponent);
      this.check(exponentiatedElements);

      var exponents = [];
      exponents.push(exponent);

      if (!preComputedValues) {
        preComputedValues = this.preCompute();
      }

      return this._getProver().prove(
          exponentiatedElements, exponents,
          box.EXPONENTIATION_PROOF_AUXILIARY_DATA, preComputedValues);
    }
  };

  /**
   * Plaintext proof generator.
   */
  box.proofs.PlaintextProofGenerator = function(publicKey, group) {

    checkDefined('public key', publicKey);
    checkDefined('group', group);

    var buildListOfBaseElements = function() {

      var list = [];
      list.push(group.getGenerator());
      list.addAll(publicKey.getGroupElementsArray());
      return list;
    };

    var buildComputationRules = function(k) {

      var rules = [];

      for (var i = 0; i < (k + 1); i++) {
        rules[i] = [];
        rules[i][0] = [];
        rules[i][0].push(i + 1);
        rules[i][0].push(1);
      }

      return rules;
    };

    var _publicKeyLength = publicKey.getGroupElementsArray().length;

    var numOutputs = _publicKeyLength + 1;
    var phiFunctionPlaintext = new box.proofs.PhiFunction(
        group, 1, numOutputs, buildListOfBaseElements(),
        buildComputationRules(_publicKeyLength));

    var _prover = new box.proofs.Prover(group, phiFunctionPlaintext);
    var _progressMeter;

    this._buildListPublicValues = function(ciphertext, plaintext) {

      var dividedCiphertext =
          mathematical.groupUtils.divide(ciphertext.getPhis(), plaintext);

      var publicValues = [];
      publicValues.push(ciphertext.getGamma());
      publicValues.addAll(dividedCiphertext);
      return publicValues;
    };

    this._getPublicKeyLength = function() {
      return _publicKeyLength;
    };

    this._getProver = function() {
      return _prover;
    };
    this._getProgressMeter = function() {
      return _progressMeter;
    };

    this.initProgressMeter = function(
        progressCallback, progressPercentMinCheckInterval) {
      _progressMeter = createProgressMeter(
          _publicKeyLength + 1, progressCallback,
          progressPercentMinCheckInterval);
      return this;
    };
  };

  box.proofs.PlaintextProofGenerator.prototype = {

    preCompute: function() {
      return this._getProver().preCompute(1, this._getProgressMeter());
    },

    generate: function(ciphertext, plaintext, witness, preComputedValues) {

      checkDefined('ciphertext', ciphertext);
      checkDefined('plaintext', plaintext);
      checkDefined('witness', witness);

      var publicKeyLength = this._getPublicKeyLength();
      var numPhis = ciphertext.getPhis().length;

      if (publicKeyLength !== numPhis) {
        throw new exceptions.CryptoLibException(
            'The length of the ciphertext must be equal to the length of the public key + 1.');
      }

      if (publicKeyLength !== plaintext.length) {
        throw new exceptions.CryptoLibException(
            'The public key and the plaintext must have the same number of elements');
      }

      if (!preComputedValues) {
        preComputedValues = this.preCompute();
      }

      var exponents = [];
      exponents.push(witness);

      return this._getProver().prove(
          this._buildListPublicValues(ciphertext, plaintext), exponents,
          box.PLAINTEXT_PROOF_AUXILIARY_DATA, preComputedValues);
    }
  };

  /**
   * Simple plaintext equality proof generator.
   */
  box.proofs.SimplePlaintextEqualityProofGenerator = function(
      primaryPublicKey, secondaryPublicKey, group) {

    checkDefined('primary public key', primaryPublicKey);
    checkDefined('secondary public key', secondaryPublicKey);
    checkDefined('group', group);

    var _primaryPublicKey = primaryPublicKey;
    var _secondaryPublicKey = secondaryPublicKey;
    var _group = group;

    var buildComputationRules = function(k) {

      var rules = [];
      var firstDimensionLength = k + 1;

      rules[0] = [];
      rules[0][0] = [];
      rules[0][0].push(1);
      rules[0][0].push(1);

      for (var i = 1; i < firstDimensionLength; i++) {
        rules[i] = [];

        rules[i][0] = [];
        rules[i][1] = [];

        rules[i][0].push(i + 1);
        rules[i][0].push(1);
        rules[i][1].push(i + 1 + k);
        rules[i][1].push(1);
      }

      return rules;
    };

    var buildSecondaryInvertedPublicKey = function() {

      var listInvertedElements = [];
      var keys = _secondaryPublicKey.getGroupElementsArray();

      for (var i = 0; i < keys.length; i++) {
        listInvertedElements.push(keys[i].invert());
      }

      return listInvertedElements;
    };

    var buildListOfBaseElements = function() {

      var invertedSecondaryKeyValues = buildSecondaryInvertedPublicKey();

      var baseElements = [];
      baseElements.push(group.getGenerator());
      baseElements.addAll(_primaryPublicKey.getGroupElementsArray());
      baseElements.addAll(invertedSecondaryKeyValues);
      return baseElements;
    };

    var primaryPublicKeyLength =
        primaryPublicKey.getGroupElementsArray().length;
    var secondaryPublicKeyLength =
        secondaryPublicKey.getGroupElementsArray().length;

    if (primaryPublicKeyLength !== secondaryPublicKeyLength) {
      throw new exceptions.CryptoLibException(
          'The number of elements in the primary and secondary public keys must be equal');
    }

    var _progressMeter;
    var _numKeyElements = primaryPublicKey.getGroupElementsArray().length;
    var numOutputs = _numKeyElements + 1;

    var rules = buildComputationRules(_numKeyElements);

    var phiFunctionSimplePlaintextEquality = new box.proofs.PhiFunction(
        group, 1, numOutputs, buildListOfBaseElements(), rules);

    var _prover =
        new box.proofs.Prover(_group, phiFunctionSimplePlaintextEquality);

    this._buildListExponents = function(witness) {

      var exponentList = [];
      exponentList.push(witness);
      return exponentList;
    };

    this._buildInputList = function(primaryCiphertext, secondaryCiphertext) {

      var dividedSubCiphertext = mathematical.groupUtils.divide(
          primaryCiphertext.getPhis(), secondaryCiphertext.getPhis());

      var generatedList = [];
      generatedList.push(secondaryCiphertext.getGamma());
      generatedList.addAll(dividedSubCiphertext);
      return generatedList;
    };

    this._getProgressMeter = function() {
      return _progressMeter;
    };

    this.initProgressMeter = function(
        progressCallback, progressPercentMinCheckInterval) {
      _progressMeter = createProgressMeter(
          _numKeyElements * 2 + 1, progressCallback,
          progressPercentMinCheckInterval);
      return this;
    };

    this._getProver = function() {
      return _prover;
    };

    this._check = function(primaryCiphertextLength) {
      if (primaryCiphertextLength !== (primaryPublicKeyLength + 1)) {
        throw new exceptions.CryptoLibException(
            'The number of elements in the ciphertext must be equal to the number in the public key + 1');
      }
    };
  };

  box.proofs.SimplePlaintextEqualityProofGenerator.prototype = {

    preCompute: function() {
      return this._getProver().preCompute(1, this._getProgressMeter());
    },

    generate: function(
        primaryCiphertext, secondaryCiphertext, witness, preComputedValues) {

      checkDefined('primary ciphertext', primaryCiphertext);
      checkDefined('secondary ciphertext', secondaryCiphertext);
      checkDefined('witness', witness);

      var primaryCiphertextLength = primaryCiphertext.getPhis().length + 1;
      var secondaryCiphertextLength = secondaryCiphertext.getPhis().length + 1;

      if (primaryCiphertextLength !== secondaryCiphertextLength) {
        throw new exceptions.CryptoLibException(
            'The number of elements in the primary and secondary ciphertext must be equal');
      }

      this._check(primaryCiphertextLength);

      if (!preComputedValues) {
        preComputedValues = this.preCompute();
      }

      return this._getProver().prove(
          this._buildInputList(primaryCiphertext, secondaryCiphertext),
          this._buildListExponents(witness),
          box.SIMPLE_PLAINTEXT_EQUALITY_PROOF_AUXILIARY_DATA,
          preComputedValues);
    }
  };

  /**
   * Plaintext equality proof generator.
   */
  box.proofs.PlaintextEqualityProofGenerator = function(
      primaryPublicKey, secondaryPublicKey, group) {

    checkDefined('primary public key', primaryPublicKey);
    checkDefined('secondary public key', secondaryPublicKey);
    checkDefined('group', group);

    var numPrimaryPublicKeyElements =
        primaryPublicKey.getGroupElementsArray().length;
    var numSecondaryPublicKeyElements =
        secondaryPublicKey.getGroupElementsArray().length;

    if (numPrimaryPublicKeyElements !== numSecondaryPublicKeyElements) {
      throw new exceptions.CryptoLibException(
          'The primary and secondary public keys contain different numbers of elements: ' +
          numPrimaryPublicKeyElements + ', ' + numSecondaryPublicKeyElements);
    }

    var _primaryPublicKey = primaryPublicKey;
    var _secondaryPublicKey = secondaryPublicKey;
    var _group = group;

    var _progressMeter;

    this._buildListExponents = function(primaryWitness, secondaryWitness) {

      var exponentList = [];
      exponentList.push(primaryWitness);
      exponentList.push(secondaryWitness);
      return exponentList;
    };

    this._buildInputList = function(primaryCiphertext, secondaryCiphertext) {

      var dividedSubCiphertext = mathematical.groupUtils.divide(
          primaryCiphertext.getPhis(), secondaryCiphertext.getPhis());

      var generatedList = [];
      generatedList.push(primaryCiphertext.getGamma());
      generatedList.push(secondaryCiphertext.getGamma());
      generatedList.addAll(dividedSubCiphertext);
      return generatedList;
    };

    function buildComputationRules(k) {
      var firstDimensionLength = k + 2;

      var rules = [];

      rules[0] = [];
      rules[0][0] = [];
      rules[0][0].push(1);
      rules[0][0].push(1);

      rules[1] = [];
      rules[1][0] = [];
      rules[1][0].push(1);
      rules[1][0].push(2);

      for (var i = 2; i < firstDimensionLength; i++) {
        rules[i] = [];

        rules[i][0] = [];
        rules[i][0].push(i);
        rules[i][0].push(1);

        rules[i][1] = [];
        rules[i][1].push(i + k);
        rules[i][1].push(2);
      }

      return rules;
    }

    function buildListOfBaseElements() {
      var invertedSecondaryKeyValues = buildSecondaryInvertedPublicKey();

      var baseElements = [];
      baseElements.push(group.getGenerator());
      baseElements.addAll(_primaryPublicKey.getGroupElementsArray());
      baseElements.addAll(invertedSecondaryKeyValues);
      return baseElements;
    }

    function buildSecondaryInvertedPublicKey() {
      var listInvertedElements = [];
      var keys = _secondaryPublicKey.getGroupElementsArray();

      for (var i = 0; i < keys.length; i++) {
        listInvertedElements.push(keys[i].invert());
      }

      return listInvertedElements;
    }

    var numKeyElements = primaryPublicKey.getGroupElementsArray().length;
    var _numOutputs = numKeyElements + 2;

    var rules = buildComputationRules(numKeyElements);

    var phiFunctionPlaintextEquality = new box.proofs.PhiFunction(
        group, 2, _numOutputs, buildListOfBaseElements(), rules);

    var _prover = new box.proofs.Prover(group, phiFunctionPlaintextEquality);

    this.getPrimaryPublicKey = function() {
      return _primaryPublicKey;
    };

    this.getSecondaryPublicKey = function() {
      return _secondaryPublicKey;
    };

    this.getGroup = function() {
      return _group;
    };

    this._getProgressMeter = function() {
      return _progressMeter;
    };

    this.initProgressMeter = function(
        progressCallback, progressPercentMinCheckInterval) {
      _progressMeter = createProgressMeter(
          numKeyElements * 2 + 2, progressCallback,
          progressPercentMinCheckInterval);
      return this;
    };

    this._getProver = function() {
      return _prover;
    };

    this.check = function(numPrimaryCiphertextElements) {
      if (numPrimaryCiphertextElements !== (numPrimaryPublicKeyElements + 1)) {
        throw new exceptions.CryptoLibException(
            'The number of ciphertext elements is not equal to the number public key elements plus one.');
      }
    };
  };

  box.proofs.PlaintextEqualityProofGenerator.prototype = {

    preCompute: function() {
      return this._getProver().preCompute(2, this._getProgressMeter());
    },

    generate: function(
        primaryCiphertext, primaryWitness, secondaryCiphertext,
        secondaryWitness, preComputedValues) {

      checkDefined('primary ciphertext', primaryCiphertext);
      checkDefined('primary witness', primaryWitness);
      checkDefined('secondary ciphertext', secondaryCiphertext);
      checkDefined('secondary witness', secondaryWitness);

      var numPrimaryCiphertextElements = primaryCiphertext.getPhis().length + 1;
      var numSecondaryCiphertextElements =
          secondaryCiphertext.getPhis().length + 1;

      if (numPrimaryCiphertextElements !== numSecondaryCiphertextElements) {
        throw new exceptions.CryptoLibException(
            'The primary and secondary ciphertexts contain different numbers of elements: ' +
            numPrimaryCiphertextElements + ', ' +
            numSecondaryCiphertextElements);
      }
      this.check(numPrimaryCiphertextElements);

      if (!preComputedValues) {
        preComputedValues = this.preCompute();
      }

      return this._getProver().prove(
          this._buildInputList(primaryCiphertext, secondaryCiphertext),
          this._buildListExponents(primaryWitness, secondaryWitness),
          box.PLAINTEXT_EQUALITY_PROOF_AUXILIARY_DATA, preComputedValues);
    }
  };

  /**
   * Plaintext exponent equality proof generator.
   */
  box.proofs.PlaintextExponentEqualityProofGenerator = function(
      baseElements, group) {

    var buildComputationRules = function(k) {

      var rules = [];
      var firstDimensionLength = k + 1;

      rules[0] = [];
      rules[0][0] = [];
      rules[0][0].push(1);
      rules[0][0].push(1);

      for (var i = 1; i < firstDimensionLength; i++) {
        rules[i] = [];

        rules[i][0] = [];
        rules[i][1] = [];

        rules[i][0].push(i + 1);
        rules[i][0].push(1);
        rules[i][1].push(i + 1 + k);
        rules[i][1].push(2);
      }

      return rules;
    };

    checkDefined('base elements', baseElements);
    checkDefined('group', group);

    var _numBaseElements = baseElements.length;

    var _progressMeter;

    var k = (_numBaseElements - 1) / 2;
    var numOutputs = k + 1;

    var phiFunctionPlaintextExponentEquality = new box.proofs.PhiFunction(
        group, 2, numOutputs, baseElements, buildComputationRules(k));

    var _prover =
        new box.proofs.Prover(group, phiFunctionPlaintextExponentEquality);

    this._buildListExponents = function(witness1, witness2) {

      var exponentList = [];
      exponentList.push(witness1);
      exponentList.push(witness2);
      return exponentList;
    };

    this._buildListPublicValues = function(ciphertext) {

      var list = [];
      list.push(ciphertext.getGamma());
      list.addAll(ciphertext.getPhis());
      return list;
    };

    this._getProver = function() {
      return _prover;
    };

    this._getProgressMeter = function() {
      return _progressMeter;
    };

    this.initProgressMeter = function(
        progressCallback, progressPercentMinCheckInterval) {
      _progressMeter = createProgressMeter(
          _numBaseElements, progressCallback, progressPercentMinCheckInterval);
      return this;
    };

    this._checkLengthOf = function(ciphertext) {
      var k = ciphertext.getPhis().length;
      var expectedNumBaseElements = (2 * k) + 1;
      if (_numBaseElements !== expectedNumBaseElements) {
        throw new exceptions.CryptoLibException(
            'The lengths of the ciphertext and the base elements do not have the expected relationship.');
      }
    };
  };

  box.proofs.PlaintextExponentEqualityProofGenerator.prototype = {

    preCompute: function() {
      return this._getProver().preCompute(2, this._getProgressMeter());
    },

    generate: function(ciphertext, witness1, witness2, preComputedValues) {

      checkDefined('ciphertext', ciphertext);
      checkDefined('witness1', witness1);
      checkDefined('witness2', witness2);

      this._checkLengthOf(ciphertext);

      if (!preComputedValues) {
        preComputedValues = this.preCompute();
      }

      return this._getProver().prove(
          this._buildListPublicValues(ciphertext),
          this._buildListExponents(witness1, witness2),
          box.PLAINTEXT_EXPONENT_EQUALITY_PROOF_AUXILIARY_DATA,
          preComputedValues);
    }
  };

  /**
   * OR-proof generator.
   */
  box.proofs.ORProofGenerator = function(publicKey, numElements, group) {

    checkPublicKey(publicKey);
    checkNumElements(numElements);
    checkGroup(group);

    var _publicKey = publicKey;
    var _numElements = numElements;
    var _group = group;
    var _numOutputs = _numElements * 2;
    var _progressMeter;

    function checkPublicKey(publicKey) {
      checkDefined('public key', publicKey);
      checkNotNull('public key', publicKey);

      var publicKeyLength = publicKey.getGroupElementsArray().length;
      if (publicKeyLength !== 1) {
        throw new exceptions.CryptoLibException(
            'Expected public key length: 1 ; Found: ' + publicKeyLength);
      }
    }

    function checkNumElements(numElements) {
      checkDefined('numElements', numElements);
      checkNotNull('numElements', numElements);

      if (numElements < 2) {
        throw new exceptions.CryptoLibException(
            'Expected number of elements to be greater than 1 ; Found: ' +
            numElements);
      }
    }

    function checkGroup(group) {
      checkDefined('group', group);
      checkNotNull('group', group);
    }

    function buildBaseElements() {
      var baseElements = [];
      baseElements.push(_group.getGenerator());
      baseElements.push(_publicKey.getGroupElementsArray()[0]);

      return baseElements;
    }

    function buildComputationRules(numOutputs) {
      var rules = [];

      var index1;
      var index2 = 0;
      for (var i = 0; i < numOutputs; i++) {
        index1 = 1 + (i % 2);
        if (i % 2 === 0) {
          index2++;
        }

        rules[i] = [];
        rules[i][0] = [];
        rules[i][0].push(index1);
        rules[i][0].push(index2);
      }

      return rules;
    }

    var phiFunctionOR = new box.proofs.PhiFunction(
        group, _numElements, _numOutputs, buildBaseElements(),
        buildComputationRules(_numOutputs));
    var _prover = new box.proofs.Prover(_group, phiFunctionOR);

    this.getNumElements = function() {
      return _numElements;
    };

    this.getProver = function() {
      return _prover;
    };

    this.buildPublicValues = function(ciphertext) {
      var publicValues = [];
      publicValues.push(ciphertext.getGamma());
      publicValues.push(ciphertext.getPhis()[0]);

      return publicValues;
    };

    this.getProgressMeter = function() {
      return _progressMeter;
    };

    this.initProgressMeter = function(
        progressCallback, progressPercentMinCheckInterval) {
      _progressMeter = createProgressMeter(
          _numOutputs, progressCallback, progressPercentMinCheckInterval);
      return this;
    };
  };

  box.proofs.ORProofGenerator.prototype = {

    preCompute: function() {

      return this.getProver().preCompute(
          this.getNumElements(), this.getProgressMeter());
    },

    generate: function(
        ciphertext, witness, elements, index, data, preComputedValues) {

      checkCiphertext(ciphertext);
      checkWitness(witness);
      checkElements(elements, this.getNumElements());
      checkIndex(index, this.getNumElements());
      checkDefined('data', data);

      function checkCiphertext(ciphertext) {
        checkDefined('ciphertext', ciphertext);
        checkNotNull('ciphertext', ciphertext);

        var ciphertextLength = ciphertext.getPhis().length + 1;
        if (ciphertextLength !== 2) {
          throw new exceptions.CryptoLibException(
              'Expected ciphertext length: 2 ; Found: ' + ciphertextLength);
        }
      }

      function checkWitness(witness) {
        checkDefined('witness', witness);
        checkNotNull('witness', witness);
      }

      function checkElements(elements, numElementsExpected) {
        checkDefined('elements', elements);
        checkNotNull('elements', elements);

        var numElementsFound = elements.length;
        if (numElementsFound !== numElementsExpected) {
          throw new exceptions.CryptoLibException(
              'Expected number of elements: ' + numElementsExpected +
              ' ; Found: ' + numElementsFound);
        }
      }

      function checkIndex(index, numElements) {
        checkDefined('index', index);
        checkNotNull('index', index);

        if (index < 0) {
          throw new exceptions.CryptoLibException(
              'Expected index of encrypted element to be positive ; Found: ' +
              index);
        }

        if (index >= numElements) {
          throw new exceptions.CryptoLibException(
              'Expected index of encrypted element to be less than number of elements: ' +
              numElements + ' ; Found: ' + index);
        }
      }

      var publicValues = this.buildPublicValues(ciphertext);

      var privateValues = [];
      privateValues.push(witness);

      if (data === null) {
        data = '';
      }

      if (!preComputedValues) {
        preComputedValues = this.preCompute();
      }

      return this.getProver().prove(
          publicValues, privateValues, data, preComputedValues, elements,
          index);
    }
  };

  /**
   * Schnorr proof verifier.
   */
  box.proofs.SchnorrProofVerifier = function(
      exponentiatedElement, voterID, electionID, proof, group) {

    checkDefined('exponentiated element', exponentiatedElement);
    checkNotEmpty('voter ID', voterID);
    checkNotEmpty('election ID', electionID);
    checkDefined('proof', proof);
    checkDefined('group', group);

    var _exponentiatedElement = exponentiatedElement;
    var _voterID = voterID;
    var _electionID = electionID;
    var _proof = proof;
    var _group = group;

    var _progressMeter;

    this._buildListOfBaseElements = function() {

      var baseElements = [];
      baseElements.push(_group.getGenerator());
      return baseElements;
    };

    this._buildExponentiatedElementsList = function() {

      var exponentiatedElements = [];
      exponentiatedElements.push(_exponentiatedElement);
      return exponentiatedElements;
    };

    this._buildDataString = function() {
      return 'SchnorrProof:VoterID=' + _voterID +
          'ElectionEventID=' + _electionID;
    };

    var computationRules = [[[1, 1]]];

    var _numOutputs = 1;
    var phiFunction = new box.proofs.PhiFunction(
        _group, 1, _numOutputs, this._buildListOfBaseElements(),
        computationRules);

    var _verifier = new box.proofs.Verifier(_group, phiFunction);

    this._getProof = function() {
      return _proof;
    };

    this._getVerifier = function() {
      return _verifier;
    };

    this._getProgressMeter = function() {
      return _progressMeter;
    };

    this.initProgressMeter = function(
        progressCallback, progressPercentMinCheckInterval) {
      _progressMeter = createProgressMeter(
          _numOutputs, progressCallback, progressPercentMinCheckInterval);
      return this;
    };
  };

  box.proofs.SchnorrProofVerifier.prototype = {

    verify: function() {
      return this._getVerifier().verify(
          this._buildExponentiatedElementsList(), this._getProof(),
          this._buildDataString(), this._getProgressMeter());
    }
  };

  /**
   * Exponentiation proof verifier.
   */
  box.proofs.ExponentiationProofVerifier = function(
      exponentiatedElements, baseElements, proof, group) {

    checkDefined('exponentiated elements', exponentiatedElements);
    checkDefined('base elements', baseElements);
    checkDefined('proof', proof);
    checkDefined('group', group);

    var _exponentiatedElements = exponentiatedElements;
    var _proof = proof;
    var _progressMeter;

    this._buildComputationRules = function(k) {

      var rules = [];

      for (var i = 0; i < k; i++) {
        rules[i] = [];
        rules[i][0] = [];
        rules[i][0].push(i + 1);
        rules[i][0].push(1);
      }

      return rules;
    };

    var _numOutputs = exponentiatedElements.length;

    var phiFunctionExponentiation = new box.proofs.PhiFunction(
        group, 1, _numOutputs, baseElements,
        this._buildComputationRules(_numOutputs));

    var _verifier = new box.proofs.Verifier(group, phiFunctionExponentiation);

    this._getProof = function() {
      return _proof;
    };

    this.getExponentiatedElements = function() {
      return _exponentiatedElements;
    };

    this._getVerifier = function() {
      return _verifier;
    };

    this._getProgressMeter = function() {
      return _progressMeter;
    };

    this.initProgressMeter = function(
        progressCallback, progressPercentMinCheckInterval) {
      _progressMeter = createProgressMeter(
          _numOutputs, progressCallback, progressPercentMinCheckInterval);
      return this;
    };
  };

  box.proofs.ExponentiationProofVerifier.prototype = {

    verify: function() {
      return this._getVerifier().verify(
          this.getExponentiatedElements(), this._getProof(),
          box.EXPONENTIATION_PROOF_AUXILIARY_DATA, this._getProgressMeter());
    }
  };

  /**
   * Plaintext proof verifier.
   */
  box.proofs.PlaintextProofVerifier = function(publicKey, group) {

    checkDefined('public key', publicKey);
    checkDefined('group', group);

    var _publicKey = publicKey;
    var _group = group;

    var _progressMeter;

    this._buildListOfBaseElements = function() {

      var list = [];
      list.push(_group.getGenerator());
      list.addAll(_publicKey.getGroupElementsArray());

      return list;
    };

    this._buildListPublicValues = function(ciphertext, plaintext) {

      var dividedCiphertext =
          mathematical.groupUtils.divide(ciphertext.getPhis(), plaintext);

      var publicValues = [];
      publicValues.push(ciphertext.getGamma());
      publicValues.addAll(dividedCiphertext);
      return publicValues;
    };

    this._buildComputationRules = function(k) {

      var rules = [];

      for (var i = 0; i < (k + 1); i++) {
        rules[i] = [];
        rules[i][0] = [];
        rules[i][0].push(i + 1);
        rules[i][0].push(1);
      }

      return rules;
    };

    var _publicKeyLength = _publicKey.getGroupElementsArray().length;
    var numOutputs = _publicKeyLength + 1;

    var phiFunctionPlaintext = new box.proofs.PhiFunction(
        _group, 1, numOutputs, this._buildListOfBaseElements(),
        this._buildComputationRules(_publicKeyLength));

    var _verifier = new box.proofs.Verifier(_group, phiFunctionPlaintext);

    this.getPublicKey = function() {
      return _publicKey;
    };

    this.getGroup = function() {
      return _group;
    };

    this._getVerifier = function() {
      return _verifier;
    };

    this._getProgressMeter = function() {
      return _progressMeter;
    };

    this.initProgressMeter = function(
        progressCallback, progressPercentMinCheckInterval) {
      _progressMeter = createProgressMeter(
          _publicKeyLength + 1, progressCallback,
          progressPercentMinCheckInterval);
      return this;
    };
  };

  box.proofs.PlaintextProofVerifier.prototype = {

    verify: function(ciphertext, plaintext, proof) {

      checkDefined('ciphertext', ciphertext);
      checkDefined('plaintext', plaintext);
      checkDefined('proof', proof);

      return this._getVerifier().verify(
          this._buildListPublicValues(ciphertext, plaintext), proof,
          box.PLAINTEXT_PROOF_AUXILIARY_DATA, this._getProgressMeter());
    }
  };

  /**
   * Simple plaintext equality proof verifier.
   */
  box.proofs.SimplePlaintextEqualityProofVerifier = function(
      primaryCiphertext, primaryPublicKey, secondaryCiphertext,
      secondaryPublicKey, proof, group) {

    checkDefined('primary ciphertext', primaryCiphertext);
    checkDefined('primary public key', primaryPublicKey);
    checkDefined('secondary ciphertext', secondaryCiphertext);
    checkDefined('secondary public key', secondaryPublicKey);
    checkDefined('proof', proof);
    checkDefined('group', group);

    var numPrimaryCiphertextElements = primaryCiphertext.getPhis().length + 1;
    var numPrimaryPublicKeyElements =
        primaryPublicKey.getGroupElementsArray().length;
    var numSecondaryCiphertextElements =
        secondaryCiphertext.getPhis().length + 1;
    var numSecondaryPublicKeyElements =
        secondaryPublicKey.getGroupElementsArray().length;

    if (numPrimaryCiphertextElements !== numSecondaryCiphertextElements) {
      throw new exceptions.CryptoLibException(
          'The primary and secondary ciphertexts contain different numbers of elements: ' +
          numPrimaryCiphertextElements + ', ' + numSecondaryCiphertextElements);
    }

    if (numPrimaryPublicKeyElements !== numSecondaryPublicKeyElements) {
      throw new exceptions.CryptoLibException(
          'The primary and secondary public keys contain different numbers of elements: ' +
          numPrimaryPublicKeyElements + ', ' + numSecondaryPublicKeyElements);
    }

    if (numPrimaryCiphertextElements !== (numPrimaryPublicKeyElements + 1)) {
      throw new exceptions.CryptoLibException(
          'The number of ciphertext elements is not equal to the number public key elements plus one.');
    }

    var _primaryCiphertext = primaryCiphertext;
    var _primaryPublicKey = primaryPublicKey;
    var _secondaryCiphertext = secondaryCiphertext;
    var _secondaryPublicKey = secondaryPublicKey;
    var _proof = proof;

    var _progressMeter;

    function buildComputationRules(k) {
      var rules = [];
      var firstDimensionLength = k + 1;

      rules[0] = [];
      rules[0][0] = [];
      rules[0][0].push(1);
      rules[0][0].push(1);

      for (var i = 1; i < firstDimensionLength; i++) {
        rules[i] = [];

        rules[i][0] = [];
        rules[i][1] = [];

        rules[i][0].push(i + 1);
        rules[i][0].push(1);
        rules[i][1].push(i + 1 + k);
        rules[i][1].push(1);
      }

      return rules;
    }

    function buildListOfBaseElements() {
      var invertedSecondaryKeyValues = buildSecondaryInvertedPublicKey();

      var baseElements = [];
      baseElements.push(group.getGenerator());
      baseElements.addAll(_primaryPublicKey.getGroupElementsArray());
      baseElements.addAll(invertedSecondaryKeyValues);
      return baseElements;
    }

    function buildSecondaryInvertedPublicKey() {
      var listInvertedElements = [];
      var keys = _secondaryPublicKey.getGroupElementsArray();

      for (var i = 0; i < keys.length; i++) {
        listInvertedElements.push(keys[i].invert());
      }

      return listInvertedElements;
    }

    var _primarySubCiphertext = _primaryCiphertext.getPhis();
    var _secondarySubCiphertext = _secondaryCiphertext.getPhis();

    var _numKeyElements = _primaryPublicKey.getGroupElementsArray().length;
    var numOutputs = _numKeyElements + 1;

    var phiFunctionSimplePlaintextEquality = new box.proofs.PhiFunction(
        group, 1, numOutputs, buildListOfBaseElements(),
        buildComputationRules(_numKeyElements));

    var _verifier =
        new box.proofs.Verifier(group, phiFunctionSimplePlaintextEquality);

    this._buildInputList = function() {

      var dividedSubCiphertext = mathematical.groupUtils.divide(
          _primarySubCiphertext, _secondarySubCiphertext);

      var generatedList = [];
      generatedList.push(_primaryCiphertext.getGamma());
      generatedList.addAll(dividedSubCiphertext);
      return generatedList;
    };

    this._getProof = function() {
      return _proof;
    };

    this._getProgressMeter = function() {
      return _progressMeter;
    };

    this.initProgressMeter = function(
        progressCallback, progressPercentMinCheckInterval) {
      _progressMeter = createProgressMeter(
          _numKeyElements * 2 + 1, progressCallback,
          progressPercentMinCheckInterval);
      return this;
    };

    this._getVerifier = function() {
      return _verifier;
    };
  };

  box.proofs.SimplePlaintextEqualityProofVerifier.prototype = {

    verify: function() {
      return this._getVerifier().verify(
          this._buildInputList(), this._getProof(),
          box.SIMPLE_PLAINTEXT_EQUALITY_PROOF_AUXILIARY_DATA,
          this._getProgressMeter());
    }
  };

  /**
   * Plaintext equality proof verifier.
   */
  box.proofs.PlaintextEqualityProofVerifier = function(
      primaryCiphertext, primaryPublicKey, secondaryCiphertext,
      secondaryPublicKey, proof, group) {

    checkDefined('primary ciphertext', primaryCiphertext);
    checkDefined('primary public key', primaryPublicKey);
    checkDefined('secondary ciphertext', secondaryCiphertext);
    checkDefined('secondary public key', secondaryPublicKey);
    checkDefined('proof', proof);
    checkDefined('group', group);

    var _primaryCiphertext = primaryCiphertext;
    var _primaryPublicKey = primaryPublicKey;
    var _secondaryCiphertext = secondaryCiphertext;
    var _secondaryPublicKey = secondaryPublicKey;
    var _proof = proof;

    var buildComputationRules = function(k) {

      var firstDimensionLength = k + 2;

      var rules = [];

      rules[0] = [];
      rules[0][0] = [];
      rules[0][0].push(1);
      rules[0][0].push(1);

      rules[1] = [];
      rules[1][0] = [];
      rules[1][0].push(1);
      rules[1][0].push(2);

      for (var i = 2; i < firstDimensionLength; i++) {
        rules[i] = [];

        rules[i][0] = [];
        rules[i][1] = [];

        rules[i][0].push(i);
        rules[i][0].push(1);
        rules[i][1].push(i + k);
        rules[i][1].push(2);
      }

      return rules;
    };

    function buildListOfBaseElements() {
      var invertedSecondaryKeyValues = buildSecondaryInvertedPublicKey();

      var baseElements = [];
      baseElements.push(group.getGenerator());
      baseElements.addAll(_primaryPublicKey.getGroupElementsArray());
      baseElements.addAll(invertedSecondaryKeyValues);
      return baseElements;
    }

    function buildSecondaryInvertedPublicKey() {
      var listInvertedElements = [];
      var keys = _secondaryPublicKey.getGroupElementsArray();

      for (var i = 0; i < keys.length; i++) {
        listInvertedElements.push(keys[i].invert());
      }

      return listInvertedElements;
    }

    var _progressMeter;

    var _primarySubCiphertext = _primaryCiphertext.getPhis();
    var _secondarySubCiphertext = _secondaryCiphertext.getPhis();

    var _numKeyElements = primaryPublicKey.getGroupElementsArray().length;
    var numOutputs = _numKeyElements + 2;

    var phiFunctionPlaintextEquality = new box.proofs.PhiFunction(
        group, 2, numOutputs, buildListOfBaseElements(),
        buildComputationRules(_numKeyElements));

    var _verifier =
        new box.proofs.Verifier(group, phiFunctionPlaintextEquality);

    this._buildInputList = function() {

      var dividedSubCiphertext = mathematical.groupUtils.divide(
          _primarySubCiphertext, _secondarySubCiphertext);

      var generatedList = [];
      generatedList.push(_primaryCiphertext.getGamma());
      generatedList.push(_secondaryCiphertext.getGamma());
      generatedList.addAll(dividedSubCiphertext);

      return generatedList;
    };

    this._getProof = function() {
      return _proof;
    };

    this._getProgressMeter = function() {
      return _progressMeter;
    };

    this.initProgressMeter = function(
        progressCallback, progressPercentMinCheckInterval) {
      _progressMeter = createProgressMeter(
          _numKeyElements * 2 + 2, progressCallback,
          progressPercentMinCheckInterval);
      return this;
    };

    this._getVerifier = function() {
      return _verifier;
    };
  };

  box.proofs.PlaintextEqualityProofVerifier.prototype = {

    verify: function() {
      return this._getVerifier().verify(
          this._buildInputList(), this._getProof(),
          box.PLAINTEXT_EQUALITY_PROOF_AUXILIARY_DATA,
          this._getProgressMeter());
    }
  };

  /**
   * Plaintext exponent equality proof verifier.
   */
  box.proofs.PlaintextExponentEqualityProofVerifier = function(
      ciphertext, baseElements, proof, group) {

    checkDefined('ciphertext', ciphertext);
    checkDefined('base elements', baseElements);
    checkDefined('proof', proof);
    checkDefined('group', group);

    var _ciphertext = ciphertext;
    var _baseElements = baseElements;
    var _proof = proof;
    var _group = group;

    var _progressMeter;

    this._buildComputationRules = function(k) {

      var rules = [];
      var firstDimensionLength = k + 1;

      rules[0] = [];
      rules[0][0] = [];
      rules[0][0].push(1);
      rules[0][0].push(1);

      for (var i = 1; i < firstDimensionLength; i++) {
        rules[i] = [];

        rules[i][0] = [];
        rules[i][1] = [];

        rules[i][0].push(i + 1);
        rules[i][0].push(1);
        rules[i][1].push(i + 1 + k);
        rules[i][1].push(2);
      }

      return rules;
    };

    this._buildListPublicValues = function() {

      var list = [];
      list.push(_ciphertext.getGamma());
      list.addAll(_ciphertext.getPhis());
      return list;
    };

    var _k = _ciphertext.getPhis().length;
    var numOutputs = _k + 1;

    var phiFunctionPlaintextExponentEquality = new box.proofs.PhiFunction(
        _group, 2, numOutputs, _baseElements, this._buildComputationRules(_k));

    var _verifier =
        new box.proofs.Verifier(_group, phiFunctionPlaintextExponentEquality);

    this._getProof = function() {
      return _proof;
    };

    this._getVerifier = function() {
      return _verifier;
    };

    this._getProgressMeter = function() {
      return _progressMeter;
    };

    this.initProgressMeter = function(
        progressCallback, progressPercentMinCheckInterval) {
      _progressMeter = createProgressMeter(
          _k * 2 + 1, progressCallback, progressPercentMinCheckInterval);
      return this;
    };
  };

  box.proofs.PlaintextExponentEqualityProofVerifier.prototype = {

    verify: function() {
      return this._getVerifier().verify(
          this._buildListPublicValues(), this._getProof(),
          box.PLAINTEXT_EXPONENT_EQUALITY_PROOF_AUXILIARY_DATA,
          this._getProgressMeter());
    }
  };

  /**
   * OR-proof verifier.
   */
  box.proofs.ORProofVerifier = function(publicKey, numElements, group) {

    checkPublicKey(publicKey);
    checkNumElements(numElements);
    checkGroup(group);

    var _publicKey = publicKey;
    var _numElements = numElements;
    var _group = group;
    var _numOutputs = _numElements * 2;
    var _progressMeter;

    function checkPublicKey(publicKey) {
      checkDefined('public key', publicKey);
      checkNotNull('public key', publicKey);

      var publicKeyLength = publicKey.getGroupElementsArray().length;
      if (publicKeyLength !== 1) {
        throw new exceptions.CryptoLibException(
            'Expected public key length: 1 ; Found: ' + publicKeyLength);
      }
    }

    function checkNumElements(numElements) {
      checkDefined('numElements', numElements);
      checkNotNull('numElements', numElements);

      if (numElements < 2) {
        throw new exceptions.CryptoLibException(
            'Expected number of elements to be greater than 1 ; Found: ' +
            numElements);
      }
    }

    function checkGroup(group) {
      checkDefined('group', group);
      checkNotNull('group', group);
    }

    function buildBaseElements() {
      var baseElements = [];
      baseElements.push(_group.getGenerator());
      baseElements.push(_publicKey.getGroupElementsArray()[0]);

      return baseElements;
    }

    function buildComputationRules(numOutputs) {
      var rules = [];

      var index1;
      var index2 = 0;
      for (var i = 0; i < numOutputs; i++) {
        index1 = 1 + (i % 2);
        if (i % 2 === 0) {
          index2++;
        }

        rules[i] = [];
        rules[i][0] = [];
        rules[i][0].push(index1);
        rules[i][0].push(index2);
      }

      return rules;
    }

    var phiFunctionOR = new box.proofs.PhiFunction(
        _group, _numElements, _numOutputs, buildBaseElements(),
        buildComputationRules(_numOutputs));
    var _verifier = new box.proofs.Verifier(_group, phiFunctionOR);

    this.buildPublicValues = function(ciphertext) {
      var publicValues = [];
      publicValues.push(ciphertext.getGamma());
      publicValues.push(ciphertext.getPhis()[0]);

      return publicValues;
    };

    this.getNumElements = function() {
      return _numElements;
    };

    this.getVerifier = function() {
      return _verifier;
    };

    this.getProgressMeter = function() {
      return _progressMeter;
    };

    this.initProgressMeter = function(
        progressCallback, progressPercentMinCheckInterval) {
      _progressMeter = createProgressMeter(
          _numOutputs, progressCallback, progressPercentMinCheckInterval);
      return this;
    };
  };

  box.proofs.ORProofVerifier.prototype = {

    verify: function(ciphertext, elements, data, proof) {

      checkCiphertext(ciphertext);
      checkElements(elements, this.getNumElements());
      checkDefined('data', data);
      checkProof(proof);

      function checkCiphertext(ciphertext) {
        checkDefined('ciphertext', ciphertext);
        checkNotNull('ciphertext', ciphertext);

        var ciphertextLength = ciphertext.getPhis().length + 1;
        if (ciphertextLength !== 2) {
          throw new exceptions.CryptoLibException(
              'Expected ciphertext length: 2 ; Found: ' + ciphertextLength);
        }
      }

      function checkElements(elements, numElementsExpected) {
        checkDefined('elements', elements);
        checkNotNull('elements', elements);

        var numElementsFound = elements.length;
        if (numElementsFound !== numElementsExpected) {
          throw new exceptions.CryptoLibException(
              'Expected number of elements: ' + numElementsExpected +
              ' ; Found: ' + numElementsFound);
        }
      }

      function checkProof(proof) {
        checkDefined('proof', proof);
        checkNotNull('proof', proof);
      }

      if (data === null) {
        data = '';
      }

      return this.getVerifier().verify(
          this.buildPublicValues(ciphertext), proof, data, elements,
          this.getProgressMeter());
    }
  };

  /**
   * Proofs factory, can be used for creating proof generators and verifiers.
   */
  box.proofs.ProofsFactory = function() {

  };

  box.proofs.ProofsFactory.prototype = {

    createSchnorrProofGenerator: function(voterID, electionID, group) {
      return new box.proofs.SchnorrProofGenerator(voterID, electionID, group);
    },

    createExponentiationProofGenerator: function(baseElements, group) {
      return new box.proofs.ExponentiationProofGenerator(baseElements, group);
    },

    createPlaintextProofGenerator: function(publicKey, group) {
      return new box.proofs.PlaintextProofGenerator(publicKey, group);
    },

    createSimplePlaintextEqualityProofGenerator: function(
        primaryPublicKey, secondaryPublicKey, group) {
      return new box.proofs.SimplePlaintextEqualityProofGenerator(
          primaryPublicKey, secondaryPublicKey, group);
    },

    createPlaintextEqualityProofGenerator: function(
        primaryPublicKey, secondaryPublicKey, group) {
      return new box.proofs.PlaintextEqualityProofGenerator(
          primaryPublicKey, secondaryPublicKey, group);
    },

    createPlaintextExponentEqualityProofGenerator: function(
        baseElements, group) {
      return new box.proofs.PlaintextExponentEqualityProofGenerator(
          baseElements, group);
    },

    createORProofGenerator: function(publicKey, numElements, group) {
      return new box.proofs.ORProofGenerator(publicKey, numElements, group);
    },

    createSchnorrProofVerifier: function(
        element, voterID, electionEventID, proof, group) {
      return new box.proofs.SchnorrProofVerifier(
          element, voterID, electionEventID, proof, group);
    },

    createExponentiationProofVerifier: function(
        exponentiatedElements, baseElements, proof, group) {
      return new box.proofs.ExponentiationProofVerifier(
          exponentiatedElements, baseElements, proof, group);
    },

    createPlaintextProofVerifier: function(
        publicKey, ciphertext, plaintext, proof, group) {
      return new box.proofs.PlaintextProofVerifier(
          publicKey, ciphertext, plaintext, proof, group);
    },

    createSimplePlaintextEqualityProofVerifier: function(
        primaryCiphertext, primaryPublicKey, secondaryCiphertext,
        secondaryPublicKey, proof, group) {
      return new box.proofs.SimplePlaintextEqualityProofVerifier(
          primaryCiphertext, primaryPublicKey, secondaryCiphertext,
          secondaryPublicKey, proof, group);
    },

    createPlaintextEqualityProofVerifier: function(
        primaryCiphertext, primaryPublicKey, secondaryCiphertext,
        secondaryPublicKey, proof, group) {
      return new box.proofs.PlaintextEqualityProofVerifier(
          primaryCiphertext, primaryPublicKey, secondaryCiphertext,
          secondaryPublicKey, proof, group);
    },

    createPlaintextExponentEqualityProofVerifier: function(
        ciphertext, baseElements, proof, group) {
      return new box.proofs.PlaintextExponentEqualityProofVerifier(
          ciphertext, baseElements, proof, group);
    },

    createORProofVerifier: function(publicKey, numElements, group) {
      return new box.proofs.ORProofVerifier(publicKey, numElements, group);
    }
  };

  /**
   * Represents the PHI function of Maurer's Unified Zero-Knowledge Proof of
   * Knowledge (ZK-PoK) scheme.
   */
  box.proofs.PhiFunction = function(
      group, numInputs, numOutputs, baseElements, computationRules) {

    var validateComputationRules = function(
        numInputs, numOutputs, baseElements, computationRules) {

      // Validate that both of the values in each of the
      // pairs of rules have values in the correct ranges
      var numBaseElements = baseElements.length;
      for (var i = 0; i < numOutputs; i++) {
        for (var j = 0; j < computationRules[i].length; j++) {
          // Validate the first value of the pair
          var pairValue1 = computationRules[i][j][0];
          if ((pairValue1 < 1) || (pairValue1 > numBaseElements)) {
            throw new exceptions.CryptoLibException(
                'Invalid first value in index pair - should be in the range 1 to ' +
                numBaseElements + ', but it was ' + pairValue1);
          }

          // Validate the second value of the pair
          var pairValue2 = computationRules[i][j][1];
          if ((pairValue2 < 1) || (pairValue2 > numInputs)) {
            throw new exceptions.CryptoLibException(
                'Invalid second value in index pair - should be in the range 1 to ' +
                numInputs + ', but it was ' + pairValue2);
          }
        }
      }
    };

    checkDefined('group', group);
    checkIsPositive('number of inputs', numInputs);
    checkIsPositive('number of outputs', numOutputs);
    checkInitializedArray('base elements', baseElements);
    checkDefined('computation rules', computationRules);
    validateComputationRules(
        numInputs, numOutputs, baseElements, computationRules);

    var _numInputs = numInputs;
    var _numOutputs = numOutputs;
    var _baseElements = baseElements;
    var _computationRules = computationRules;

    /**
     * @return The number of inputs
     */
    this.getNumInputs = function() {
      return _numInputs;
    };

    /**
     * @return The number of outputs
     */
    this.getNumOutputs = function() {
      return _numOutputs;
    };

    /**
     * @return The list of base elements
     */
    this.getBaseElements = function() {
      return _baseElements;
    };

    /**
     * @return The computation rules.
     */
    this.getComputationRules = function() {
      return _computationRules;
    };
  };

  box.proofs.PhiFunction.prototype = {

    calculatePhi: function(inputs, progressMeter) {

      checkDefined('inputs', inputs);
      if (progressMeter === undefined) {
        progressMeter = new utils.ProgressMeter();
      }
      checkDefined('progressMeter', progressMeter);

      var computationsRules = this.getComputationRules();
      var baseElements = this.getBaseElements();

      var partialResult;
      var resultForThisListOfPairs;

      var result = [];
      var progress = 0;

      for (var i = 0; i < computationsRules.length; i++) {
        var numPairsInList = computationsRules[i].length;

        resultForThisListOfPairs =
            baseElements[computationsRules[i][0][0] - 1].exponentiate(
                inputs[computationsRules[i][0][1] - 1]);

        progressMeter.update(++progress);

        for (var j = 1; j < numPairsInList; j++) {
          var index1 = computationsRules[i][j][0];
          index1 = index1 - 1;
          var index2 = computationsRules[i][j][1];
          index2 = index2 - 1;

          partialResult = baseElements[index1].exponentiate(inputs[index2]);

          resultForThisListOfPairs =
              resultForThisListOfPairs.multiply(partialResult);

          progressMeter.update(++progress);
        }
        result.push(resultForThisListOfPairs);
      }

      return result;
    }
  };

  /**
   * Acts as a prover in a Zero-Knowledge Proof of Knowledge (ZK-PoK) exchange
   * using Maurer's unified PHI function.
   */
  box.proofs.Prover = function(group, phiFunction) {

    checkDefined('group', group);
    checkDefined('phiFunction', phiFunction);

    var _group = group;
    var _phiFunction = phiFunction;
    var _cryptoRandomInteger = secureRandomFactory.getCryptoRandomInteger();

    /**
     * @return Get the group.
     */
    this.getGroup = function() {
      return _group;
    };

    /**
     * @return Get the phi function.
     */
    this.getPhiFunction = function() {
      return _phiFunction;
    };

    this._calculateRandomExponents = function(numExponentsNeeded) {

      var exponentsArray = [];

      for (var i = 0; i < numExponentsNeeded; i++) {
        var randomExponent = mathematical.groupUtils.generateRandomExponent(
            _group, _cryptoRandomInteger);

        exponentsArray.push(randomExponent);
      }

      return exponentsArray;
    };

    this._generateHash = function(publicValues, phiOutputs, data) {

      var hashBuilder = new box.proofs.HashBuilder();

      var hashB64 =
          hashBuilder.buildHashForProof(publicValues, phiOutputs, data);

      var hash = converters.base64Decode(hashB64);
      var hashBytes = converters.bytesFromString(hash);
      hashBytes.unshift(0);

      var value = new box.forge.jsbn.BigInteger(hashBytes);

      return new mathematical.Exponent(_group.getQ(), value);
    };

    this._generateValuesList = function(
        privateValues, hash, randomExponents, index) {

      var proofValues = [];
      var proofValue;
      if (typeof index === 'undefined') {
        for (var i = 0; i < privateValues.length; i++) {
          proofValue = randomExponents[i].getValue().add(
              privateValues[i].getValue().multiply(hash.getValue()));

          proofValues.push(
              new mathematical.Exponent(_group.getQ(), proofValue));
        }
      } else {
        var numElements = randomExponents.length / 2;

        var updatedHash = hash;
        for (var j = 0; j < numElements; j++) {
          if (j === index) {
            for (var k = 0; k < numElements; k++) {
              if (k !== index) {
                updatedHash = updatedHash.subtract(randomExponents[k]);
              }
            }
            proofValues.push(updatedHash);
          } else {
            proofValues.push(randomExponents[j]);
          }
        }

        for (var m = numElements; m < (numElements * 2); m++) {
          if (m === (numElements + index)) {
            proofValue = randomExponents[m].getValue().add(
                privateValues[0].getValue().multiply(updatedHash.getValue()));

            proofValues.push(
                new mathematical.Exponent(_group.getQ(), proofValue));
          } else {
            proofValues.push(randomExponents[m]);
          }
        }
      }

      return proofValues;
    };

    this._updatePreComputedValues = function(
        preComputedValues, publicValues, elements, index) {

      var gamma = publicValues[0];
      var phi = publicValues[1];
      var numElements = elements.length;

      var challenges = this._calculateRandomExponents(numElements);

      var updatedExponents = [];
      updatedExponents.addAll(challenges);
      updatedExponents.addAll(preComputedValues.getExponents());

      var updatedPhiOutputs = [];
      var phiOutputs = preComputedValues.getPhiOutputs();
      for (var j = 0; j < numElements; j++) {
        var offset = 2 * j;
        if (j === index) {
          updatedPhiOutputs.push(phiOutputs[offset]);
          updatedPhiOutputs.push(phiOutputs[offset + 1]);
        } else {
          var gammaFactor = gamma.invert().exponentiate(challenges[j]);
          var phiFactor =
              (phi.invert().multiply(elements[j])).exponentiate(challenges[j]);
          updatedPhiOutputs.push(phiOutputs[offset].multiply(gammaFactor));
          updatedPhiOutputs.push(phiOutputs[offset + 1].multiply(phiFactor));
        }
      }

      return new box.proofs.ProofPreComputedValues(
          updatedExponents, updatedPhiOutputs);
    };
  };

  box.proofs.Prover.prototype = {

    /**
     * Pre-compute time-consuming values, so they can be used later for
     * method prove().
     */
    preCompute: function(privateValuesLength, progressMeter) {
      var randomExponents = this._calculateRandomExponents(privateValuesLength);

      var phiOutputs =
          this.getPhiFunction().calculatePhi(randomExponents, progressMeter);

      return new box.proofs.ProofPreComputedValues(randomExponents, phiOutputs);
    },

    /**
     * Performs prover steps in a ZK-PoK exchange using Maurer's unified PHI
     * function. This involves generating a proof, using the received inputs
     * and this Prover's internal fields.
     */
    prove: function(
        publicValues, privateValues, data, preComputedValues, elements, index) {

      checkDefined('public values', publicValues);
      checkDefined('private values', privateValues);
      checkDefined('data', data);

      if (!preComputedValues ||
          typeof preComputedValues.getPhiOutputs() === 'undefined' ||
          typeof preComputedValues.getExponents() === 'undefined') {
        throw new exceptions.CryptoLibException(
            'The precomputed values must be initialized');
      }

      if (typeof elements !== 'undefined') {
        preComputedValues = this._updatePreComputedValues(
            preComputedValues, publicValues, elements, index);
      }

      var hash = this._generateHash(
          publicValues, preComputedValues.getPhiOutputs(), data);

      var proofValues = this._generateValuesList(
          privateValues, hash, preComputedValues.getExponents(), index);

      return new box.proofs.Proof(hash, proofValues);
    }
  };

  /**
   * Acts as a verifier in a Zero-Knowledge Proof of Knowledge (ZK-PoK)
   * exchange using Maurer's unified PHI function.
   */
  box.proofs.Verifier = function(group, phiFunction) {

    checkDefined('group', group);
    checkDefined('phiFunction', phiFunction);

    var _group = group;
    var _phiFunction = phiFunction;

    /**
     * @return Get the group.
     */
    this.getGroup = function() {
      return _group;
    };

    /**
     * @return Get the phi function.
     */
    this.getPhiFunction = function() {
      return _phiFunction;
    };

    this._calculateComputedValues = function(
        publicValues, phiOutputs, proofHashValueOrElements, challenges) {

      var proofHashValue;
      var elements;
      if (!isArray(proofHashValueOrElements)) {
        proofHashValue = proofHashValueOrElements;
      } else {
        elements = proofHashValueOrElements;
      }

      var computedValues = [];
      if (typeof proofHashValue !== 'undefined') {
        for (var i = 0; i < phiOutputs.length; i++) {
          computedValues.push(phiOutputs[i].multiply(
              publicValues[i].exponentiate(proofHashValue.negate())));
        }
      } else {
        var gamma = publicValues[0];
        var phi = publicValues[1];

        for (var j = 0; j < challenges.length; j++) {
          var offset = 2 * j;
          var gammaFactor = gamma.invert().exponentiate(challenges[j]);
          var phiFactor =
              (phi.invert().multiply(elements[j])).exponentiate(challenges[j]);
          computedValues.push(phiOutputs[offset].multiply(gammaFactor));
          computedValues.push(phiOutputs[offset + 1].multiply(phiFactor));
        }
      }

      return computedValues;
    };

    this._calculateHash = function(publicValues, computedValues, data) {

      var hashBuilder = new box.proofs.HashBuilder();

      var hashB64 =
          hashBuilder.buildHashForProof(publicValues, computedValues, data);

      var hash = converters.base64Decode(hashB64);

      var hashBytes = converters.bytesFromString(hash);
      hashBytes.unshift(0);

      var value = new box.forge.jsbn.BigInteger(hashBytes);

      return new mathematical.Exponent(_group.getQ(), value);
    };
  };

  box.proofs.Verifier.prototype = {

    /**
     * Verifies the received input as being true or false.
     */
    verify: function(
        publicValues, proof, data, elementsOrProgressMeter, progressMeter) {

      checkDefined('public values', publicValues);
      checkDefined('proof', proof);
      checkDefined('data', data);

      var elements;
      if (!isArray(elementsOrProgressMeter)) {
        progressMeter = elementsOrProgressMeter;
      } else {
        elements = elementsOrProgressMeter;
      }

      var proofHashValue = proof.getHash();

      var phiOutputs;
      var computedValues;
      if (typeof elements === 'undefined') {
        phiOutputs = this.getPhiFunction().calculatePhi(
            proof.getValues(), progressMeter);
        computedValues = this._calculateComputedValues(
            publicValues, phiOutputs, proofHashValue);
      } else {
        var numElements = elements.length;
        var proofValues = proof.getValues();
        var challenges = proofValues.slice(0, numElements);
        var phiInputs = proofValues.slice(numElements, numElements * 2);

        phiOutputs =
            this.getPhiFunction().calculatePhi(phiInputs, progressMeter);
        computedValues = this._calculateComputedValues(
            publicValues, phiOutputs, elements, challenges);
      }

      var calculatedHash =
          this._calculateHash(publicValues, computedValues, data);

      return proofHashValue.equals(calculatedHash);
    }
  };

  /**
   * @class ProofPreComputedValues stores randomly created exponents and
   *        outputs of box.proofs.PhiFunction.calculatePhi().
   */
  box.proofs.ProofPreComputedValues = function(exponents, phiOutputs) {

    this.className = 'ProofPreComputedValues';
    var _exponents = exponents;
    var _phiOutputs = phiOutputs;

    /**
     * @return the exponents.
     */
    this.getExponents = function() {
      return _exponents;
    };

    /**
     * @return the phi function output.
     */
    this.getPhiOutputs = function() {
      return _phiOutputs;
    };
  };

  box.proofs.ProofPreComputedValues.prototype = {

    stringify: function() {
      var exponentValues = Array();
      var phiOutputValues = Array();

      for (var i = 0; i < this.getExponents().length; i++) {
        exponentValues[i] =
            converters.base64FromBigInteger(this.getExponents()[i].getValue());
      }

      for (var j = 0; j < this.getPhiOutputs().length; j++) {
        phiOutputValues[j] = converters.base64FromBigInteger(
            this.getPhiOutputs()[j].getElementValue());
      }

      return JSON.stringify({
        preComputed: {
          p: converters.base64FromBigInteger(this.getPhiOutputs()[0].getP()),
          q: converters.base64FromBigInteger(this.getPhiOutputs()[0].getQ()),
          exponents: exponentValues,
          phiOutputs: phiOutputValues
        }
      });
    }
  };

  /**
   * Represents a proof in a ZKP exchange.
   */
  box.proofs.Proof = function(hashValue, values) {

    checkDefined('hash value', hashValue);
    checkDefined('values', values);

    var _hashValue = hashValue;

    var _values = values;

    /**
     * @return Get the hash value of this proof.
     */
    this.getHash = function() {
      return _hashValue;
    };

    /**
     * @return Get the list of values of this proof.
     */
    this.getValues = function() {
      return _values;
    };
  };

  box.proofs.Proof.prototype = {

    /**
     * @return true if the given proof has the same hash and values.
     */
    equals: function(proof) {
      return proof.getHash().equals(this.getHash()) &&
          proof.getValues().equals(this.getValues());
    },

    /**
     * Displays a string representation of this proof JSON.
     * <p>
     * Note: in order to permit interoperability between libraries, this
     * representation should match the equivalent representation in Java.
     *
     * @function
     * @returns a string representation of this proof JSON.
     */
    stringify: function() {

      var values = Array();

      for (var i = 0; i < this.getValues().length; i++) {
        values[i] =
            converters.base64FromBigInteger(this.getValues()[i].getValue());
      }

      return JSON.stringify({
        zkProof: {
          q: converters.base64FromBigInteger(this.getHash().getQ()),
          hash: converters.base64FromBigInteger(this.getHash().getValue()),
          values: values
        }
      });
    }
  };

  /**
   * A class which provides utility methods for generating a hash from a set
   * of inputs.
   */
  box.proofs.HashBuilder = function() {

    /**
     * Convert an integer to a byte array
     */
    this._getInt64Bytes = function(x) {
      var bytes = [];
      var i = 8;
      do {
        bytes[--i] = x & (255);
        x = x >> 8;
      } while (i);

      return bytes;
    };

    /**
     * Converts an array to a String.
     */
    this._arrayElementValuesToString = function(groupElements) {

      var str = '';

      for (var i = 0; i < groupElements.length; i++) {
        if (groupElements[i].getElementValue() !== undefined) {
          str += groupElements[i].getElementValue();
        } else {
          throw new exceptions.CryptoLibException(
              'Group Element must implement getElementValue() method.');
        }
      }
      return str;
    };
  };

  box.proofs.HashBuilder.prototype = {

    /**
     * Generates a hash value based on the element values of publicValues
     * and generatedValues and the data.
     *
     * @function
     * @param publicValues
     *            {array} group elements representing public values.
     * @param generatedValues
     *            {array} group elements representing generated values.
     * @param data
     *            {string} some data encoded as a String.
     * @returns a generated hash code.
     * @throws CryptoLibException
     *             if any of the received objects has unsupported type.
     */
    buildHashForProof: function(publicValues, generatedValues, data) {

      checkInitializedArray('public values', publicValues);
      checkInitializedArray('generated values', generatedValues);

      var messageDigester = messageDigestFactory.getCryptoMessageDigest();

      messageDigester.start();

      messageDigester = update(
          messageDigester, this._arrayElementValuesToString(publicValues));
      messageDigester = update(
          messageDigester, this._arrayElementValuesToString(generatedValues));
      messageDigester = update(messageDigester, data);

      return messageDigester.digest();
    }
  };

  /**
   * Utility method that updates messageDigester with the provided string.
   */
  function update(messageDigester, str) {
    var stringRepresentationB64 = converters.base64Encode(str);
    var arrayToHash = [];
    arrayToHash.push(stringRepresentationB64);
    messageDigester.update(arrayToHash);
    return messageDigester;
  }

  /**
   * Utility method that checks that value is defined and not null and throws
   * related exception otherwise.
   */
  function checkDefined(key, value) {
    if (typeof value === 'undefined') {
      throw new exceptions.CryptoLibException(
          'The ' + key + ' must be defined');
    }
  }

  /**
   * Utility method that checks that value is initialized array and throws
   * related exception otherwise.
   */
  function checkInitializedArray(key, value) {
    checkDefined(key, value);

    if (!isArray(value)) {
      throw new exceptions.CryptoLibException(
          'The ' + key + ' must be an initialized array');
    }
  }

  /**
   * Utility method that checks that value is not null and throws related
   * exception otherwise.
   */
  function checkNotNull(key, value) {
    checkDefined(key, value);

    if (value === null) {
      throw new exceptions.CryptoLibException(
          'The ' + key + ' must not be null');
    }
  }

  /**
   * Utility method that checks that value is not empty and throws related
   * exception otherwise.
   */
  function checkNotEmpty(key, value) {
    checkDefined(key, value);

    if (value.length === 0) {
      throw new exceptions.CryptoLibException(
          'The ' + key + ' must not be empty');
    }
  }

  /**
   * Utility method that checks that value is positive and throws related
   * exception otherwise.
   */
  function checkIsPositive(key, value) {
    checkDefined(key, value);

    if (isNaN(value)) {
      throw new exceptions.CryptoLibException(
          'The ' + key + ' must be a number; Found ' + value);
    }

    if (value <= 0) {
      throw new exceptions.CryptoLibException(
          'The ' + key + ' must be positive; Found ' + value);
    }
  }

  /**
   * Utility method that checks that values are defined and throws related
   * exception otherwise.
   */
  var checkDefinedProgressParams = function(
      progressCallback, progressPercentMinCheckInterval) {
    if (typeof progressCallback !== 'undefined' && 
        typeof progressCallback !== 'function') {
        throw new exceptions.CryptoLibException(
            'The progress callback function provided as input is  not a function');
    }
    if (typeof progressPercentMinCheckInterval !== 'undefined' &&
        isNaN(progressPercentMinCheckInterval)) {
        throw new exceptions.CryptoLibException(
            'The progress percentage check interval provided as input is not a number');
    }
  };

  function isArray(a) {
    return (!!a) && (a.constructor === Array);
  }

  /**
   * Utility method that creates a new instance of ProgressMeter.
   */
  function createProgressMeter(
      progressMax, progressCallback, progressPercentMinCheckInterval) {
    checkDefinedProgressParams(
        progressCallback, progressPercentMinCheckInterval);
    return new utils.ProgressMeter(
        progressMax, progressCallback, progressPercentMinCheckInterval);
  }

  /**
   * Provides utility functionality for working with proofs.
   *
   * @class utils
   * @memberof proofs
   */
  box.proofs.utils = (function() {

    /**
     * Deserializes a Proof JSON string representation to a Proof object.
     *
     * @function
     * @memberof proofs.utils
     * @param jsonAsString
     *            {JSON} a JSON representation of a proof.
     * @returns a new Proof, created from the received string.
     */
    function deserializeProof(jsonAsString) {
      var proofJson = JSON.parse(jsonAsString).zkProof;

      var q = converters.base64ToBigInteger(proofJson.q);
      var hash = new box.commons.mathematical.Exponent(
          q, converters.base64ToBigInteger(proofJson.hash));
      var values = [];
      for (var i = 0; i < proofJson.values.length; i++) {
        values.push(new box.commons.mathematical.Exponent(
            q, converters.base64ToBigInteger(proofJson.values[i])));
      }

      return new box.proofs.Proof(hash, values);
    }

    /**
     * Deserializes a ProofPreComputedValues JSON string representation to a
     * ProofPreComputedValues object.
     *
     * @function
     * @memberof proofs.utils
     * @param jsonAsString
     *            {JSON} a JSON representation of a pre-computed value.
     * @returns a new ProofPreComputedValues, created from the received
     *          string.
     */
    function deserializeProofPreComputedValues(jsonAsString) {
      var proofPreComputedValuesJson = JSON.parse(jsonAsString).preComputed;
      var p = converters.base64ToBigInteger(proofPreComputedValuesJson.p);
      var q = converters.base64ToBigInteger(proofPreComputedValuesJson.q);

      var exponentValues = proofPreComputedValuesJson.exponents;
      var exponents = [];
      var value;
      for (var i = 0; i < exponentValues.length; i++) {
        value = converters.base64ToBigInteger(exponentValues[i]);
        exponents.push(new box.commons.mathematical.Exponent(q, value));
      }

      var phiOutputValues = proofPreComputedValuesJson.phiOutputs;
      var phiOutputs = [];
      for (var j = 0; j < phiOutputValues.length; j++) {
        value = converters.base64ToBigInteger(phiOutputValues[j]);
        phiOutputs.push(
            new box.commons.mathematical.ZpGroupElement(value, p, q));
      }

      return new box.proofs.ProofPreComputedValues(exponents, phiOutputs);
    }

    return {
      deserializeProof: deserializeProof,
      deserializeProofPreComputedValues: deserializeProofPreComputedValues
    };
  })();
};

/*
 * 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
 */
cryptolib.modules.proofs = cryptolib.modules.proofs || {};

/**
 * Allows several cryptographic proofs to be generated and verified.
 * <P>
 */
cryptolib.modules.proofs.service = function(box) {
  'use strict';

  box.proofs = box.proofs || {};
  box.proofs.service = {};

  var proofsFactory;

  var f = function(box) {
    proofsFactory = new box.proofs.ProofsFactory();
  };

  f.policies = {
    proofs: {
      messagedigest: {
        algorithm: box.policies.proofs.messagedigest.algorithm,
        provider: box.policies.proofs.messagedigest.provider
      },
      secureRandom: {provider: box.policies.proofs.secureRandom.provider},
      charset: box.policies.proofs.charset
    },
    primitives: {
      secureRandom: {provider: box.policies.primitives.secureRandom.provider}
    }
  };

  cryptolib(
      'proofs.implementation', 'primitives.securerandom', 'commons.exceptions',
      'commons.mathematical', f);

  /**
   * Creates a Schnorr proof.
   *
   * @param voterID
   *            The voter ID. This should be a string.
   * @param electionID
   *            The election event ID. This should be a string.
   * @param exponentiatedElement
   *            The exponentiated element. This should be an element of the
   *            received group.
   * @param witness
   *            The witness. This should be an Exponent of the received group.
   * @param group
   *            The mathematical group. This should be a Zp subgroup.
   * @param progressCallbackOrPreComputedValues
   *            Progress callback function or pre-computed values (optional).
   * @param progressPercentMinCheckInterval
   *            Progress percentage minimum check interval, if the previous
   *            parameter is a progressCallback function (optional).
   *
   * @returns The Schnorr proof.
   */
  box.proofs.service.createSchnorrProof = function(
      voterID, electionID, exponentiatedElement, witness, group,
      progressCallbackOrPreComputedValues, progressPercentMinCheckInterval) {

    var proofGenerator =
        proofsFactory.createSchnorrProofGenerator(voterID, electionID, group);

    if (!progressCallbackOrPreComputedValues) {
      return proofGenerator.generate(exponentiatedElement, witness);
    } else if (isPreComputedValues(progressCallbackOrPreComputedValues)) {
      var preComputedValues = progressCallbackOrPreComputedValues;
      return proofGenerator.generate(
          exponentiatedElement, witness, preComputedValues);
    } else {
      var progressCallback = progressCallbackOrPreComputedValues;
      return proofGenerator
          .initProgressMeter(progressCallback, progressPercentMinCheckInterval)
          .generate(exponentiatedElement, witness);
    }
  };

  /**
   * Creates an exponentiation proof.
   *
   * @param exponentiatedElements
   *            The exponentiated elements. These should consist of the
   *            following base elements, exponentiated with the specified
   *            exponent.
   * @param baseElements
   *            The base elements used for the exponentiations. These should
   *            correspond to elements of the specified group.
   * @param exponent
   *            The exponent for which this proof is to be generated. It
   *            should be an exponent of the specified group.
   * @param group
   *            The mathematical group. This should be a Zp subgroup.
   * @param progressCallbackOrPreComputedValues
   *            Progress callback function or pre-computed values (optional).
   * @param progressPercentMinCheckInterval
   *            Progress percentage minimum check interval, if the previous
   *            parameter is a progressCallback function (optional).
   *
   * @returns The exponentiation proof.
   */
  box.proofs.service.createExponentiationProof = function(
      exponentiatedElements, baseElements, exponent, group,
      progressCallbackOrPreComputedValues, progressPercentMinCheckInterval) {

    var proofGenerator =
        proofsFactory.createExponentiationProofGenerator(baseElements, group);

    if (!progressCallbackOrPreComputedValues) {
      return proofGenerator.generate(exponentiatedElements, exponent);
    } else if (isPreComputedValues(progressCallbackOrPreComputedValues)) {
      var preComputedValues = progressCallbackOrPreComputedValues;
      return proofGenerator.generate(
          exponentiatedElements, exponent, preComputedValues);
    } else {
      var progressCallback = progressCallbackOrPreComputedValues;
      return proofGenerator
          .initProgressMeter(progressCallback, progressPercentMinCheckInterval)
          .generate(exponentiatedElements, exponent);
    }
  };

  /**
   * Creates a plaintext proof.
   *
   * @param publicKey
   *            The primary public key. This should be an ElGamal public key.
   * @param ciphertext
   *            The ciphertext. This should be the output from ElGamal
   *            encryption.
   * @param plaintext
   *            The plaintext. This should be an array of elements of the
   *            received group.
   * @param witness
   *            The witness. This should be an Exponent of the received group.
   * @param group
   *            The mathematical group. This should be a Zp subgroup.
   * @param progressCallbackOrPreComputedValues
   *            Progress callback function or pre-computed values (optional).
   * @param progressPercentMinCheckInterval
   *            Progress percentage minimum check interval, if the previous
   *            parameter is a progressCallback function (optional).
   *
   * @returns The plaintext proof.
   */
  box.proofs.service.createPlaintextProof = function(
      publicKey, ciphertext, plaintext, witness, group,
      progressCallbackOrPreComputedValues, progressPercentMinCheckInterval) {

    var proofGenerator =
        proofsFactory.createPlaintextProofGenerator(publicKey, group);

    if (!progressCallbackOrPreComputedValues) {
      return proofGenerator.generate(ciphertext, plaintext, witness);
    } else if (isPreComputedValues(progressCallbackOrPreComputedValues)) {
      var preComputedValues = progressCallbackOrPreComputedValues;
      return proofGenerator.generate(
          ciphertext, plaintext, witness, preComputedValues);
    } else {
      var progressCallback = progressCallbackOrPreComputedValues;
      return proofGenerator
          .initProgressMeter(progressCallback, progressPercentMinCheckInterval)
          .generate(ciphertext, plaintext, witness);
    }
  };

  /**
   * Creates a simple plaintext equality Proof.
   *
   * @param primaryCiphertext
   *            The primary ciphertext. This should be the output from ElGamal
   *            encryption.
   * @param primaryPublicKey
   *            The primary public key. This should be an ElGamal public key.
   * @param witness
   *            The witness of the proof. This should be an Exponent of the
   *            received group.
   * @param secondaryCiphertext
   *            The secondary ciphertext. This should be the output from
   *            ElGamal encryption.
   * @param secondaryPublicKey
   *            The secondary public key. This should be an ElGamal public
   *            key.
   * @param group
   *            The mathematical group. This should be a Zp subgroup.
   * @param progressCallbackOrPreComputedValues
   *            Progress callback function or pre-computed values (optional).
   * @param progressPercentMinCheckInterval
   *            Progress percentage minimum check interval, if the previous
   *            parameter is a progressCallback function (optional).
   *
   * @returns The simple plaintext equality proof.
   */
  box.proofs.service.createSimplePlaintextEqualityProof = function(
      primaryCiphertext, primaryPublicKey, witness, secondaryCiphertext,
      secondaryPublicKey, group, progressCallbackOrPreComputedValues,
      progressPercentMinCheckInterval) {

    var proofGenerator =
        proofsFactory.createSimplePlaintextEqualityProofGenerator(
            primaryPublicKey, secondaryPublicKey, group);

    if (!progressCallbackOrPreComputedValues) {
      return proofGenerator.generate(
          primaryCiphertext, secondaryCiphertext, witness);
    } else if (isPreComputedValues(progressCallbackOrPreComputedValues)) {
      var preComputedValues = progressCallbackOrPreComputedValues;
      return proofGenerator.generate(
          primaryCiphertext, secondaryCiphertext, witness, preComputedValues);
    } else {
      var progressCallback = progressCallbackOrPreComputedValues;
      return proofGenerator
          .initProgressMeter(progressCallback, progressPercentMinCheckInterval)
          .generate(primaryCiphertext, secondaryCiphertext, witness);
    }
  };

  /**
   * Creates a plaintext equality proof.
   *
   * @param primaryCiphertext
   *            The primary ciphertext. This should be the output from ElGamal
   *            encryption.
   * @param primaryPublicKey
   *            The primary public key. This should be an ElGamal public key.
   * @param primaryWitness
   *            The primary witness of the proof. This should be an Exponent
   *            of the received group.
   * @param secondaryCiphertext
   *            The secondary ciphertext. This should be the output from
   *            ElGamal encryption.
   * @param secondaryPublicKey
   *            The secondary public key. This should be an ElGamal public
   *            key.
   * @param secondaryWitness
   *            The secondary witness of the proof. This should be an Exponent
   *            of the received group.
   * @param group
   *            The mathematical group. This should be a Zp subgroup.
   * @param progressCallbackOrPreComputedValues
   *            Progress callback function or pre-computed values (optional).
   * @param progressPercentMinCheckInterval
   *            Progress percentage minimum check interval, if the previous
   *            parameter is a progressCallback function (optional).
   *
   * @returns The plaintext equality proof.
   */
  box.proofs.service.createPlaintextEqualityProof = function(
      primaryCiphertext, primaryPublicKey, primaryWitness, secondaryCiphertext,
      secondaryPublicKey, secondaryWitness, group,
      progressCallbackOrPreComputedValues, progressPercentMinCheckInterval) {

    var proofGenerator = proofsFactory.createPlaintextEqualityProofGenerator(
        primaryPublicKey, secondaryPublicKey, group);

    if (!progressCallbackOrPreComputedValues) {
      return proofGenerator.generate(
          primaryCiphertext, primaryWitness, secondaryCiphertext,
          secondaryWitness);
    } else if (isPreComputedValues(progressCallbackOrPreComputedValues)) {
      var preComputedValues = progressCallbackOrPreComputedValues;
      return proofGenerator.generate(
          primaryCiphertext, primaryWitness, secondaryCiphertext,
          secondaryWitness, preComputedValues);
    } else {
      var progressCallback = progressCallbackOrPreComputedValues;
      return proofGenerator
          .initProgressMeter(progressCallback, progressPercentMinCheckInterval)
          .generate(
              primaryCiphertext, primaryWitness, secondaryCiphertext,
              secondaryWitness);
    }
  };

  /**
   * Creates a plaintext exponent equality proof.
   *
   * @param ciphertext
   *            The ciphertext. This should be the output from ElGamal
   *            encryption.
   * @param plaintext
   *            The plaintext. It should be an array of members of the
   *            received group.
   * @param primaryWitness
   *            The primary witness. This should be an Exponent of the
   *            received group.
   * @param secondaryWitness
   *            The secondary witness. This should be an Exponent of the
   *            received group.
   * @param group
   *            The mathematical group. This should be a Zp subgroup.
   * @param progressCallbackOrPreComputedValues
   *            Progress callback function or pre-computed values (optional).
   * @param progressPercentMinCheckInterval
   *            Progress percentage minimum check interval, if the previous
   *            parameter is a progressCallback function (optional).
   *
   * @returns The plaintext exponent equality proof.
   */
  box.proofs.service.createPlaintextExponentEqualityProof = function(
      ciphertext, plaintext, primaryWitness, secondaryWitness, group,
      progressCallbackOrPreComputedValues, progressPercentMinCheckInterval) {

    var proofGenerator =
        proofsFactory.createPlaintextExponentEqualityProofGenerator(
            plaintext, group);

    if (!progressCallbackOrPreComputedValues) {
      return proofGenerator.generate(
          ciphertext, primaryWitness, secondaryWitness);
    } else if (isPreComputedValues(progressCallbackOrPreComputedValues)) {
      var preComputedValues = progressCallbackOrPreComputedValues;
      return proofGenerator.generate(
          ciphertext, primaryWitness, secondaryWitness, preComputedValues);
    } else {
      var progressCallback = progressCallbackOrPreComputedValues;
      return proofGenerator
          .initProgressMeter(progressCallback, progressPercentMinCheckInterval)
          .generate(ciphertext, primaryWitness, secondaryWitness);
    }
  };

  /**
   * Creates an OR-proof.
   *
   * @param publicKey
   *            The public key. This should be an ElGamal public key.
   * @param ciphertext
   *            The ciphertext of the mathematical group element that was
   *            encrypted. This should be the output from ElGamal encryption.
   * @param witness
   *            The witness used to generate the ciphertext. This should be an
   *            Exponent of the received group.
   * @param elements
   *            The mathematical group elements, one of which was encrypted.
   *            This should be an array of elements of the received group.
   * @param index
   *            The index of the mathematical group element that was
   *            encrypted. This should be an integer.
   * @param data
   *            Any extra data to use for the proof generation. This should be
   *            a String. NOTE: If this parameter is null, then data will be set
   *            to empty string.
   * @param group
   *            The mathematical group. This should be a Zp subgroup.
   * @param progressCallbackOrPreComputedValues
   *            Progress callback function or pre-computed values (optional).
   * @param progressPercentMinCheckInterval
   *            Progress percentage minimum check interval, if the previous
   *            parameter is a progressCallback function (optional).
   *
   * @returns The OR-proof.
   */
  box.proofs.service.createORProof = function(
      publicKey, ciphertext, witness, elements, index, data, group,
      progressCallbackOrPreComputedValues, progressPercentMinCheckInterval) {

    var proofGenerator =
        proofsFactory.createORProofGenerator(publicKey, elements.length, group);

    if (!progressCallbackOrPreComputedValues) {
      return proofGenerator.generate(
          ciphertext, witness, elements, index, data);
    } else if (isPreComputedValues(progressCallbackOrPreComputedValues)) {
      var preComputedValues = progressCallbackOrPreComputedValues;
      return proofGenerator.generate(
          ciphertext, witness, elements, index, data, preComputedValues);
    } else {
      var progressCallback = progressCallbackOrPreComputedValues;
      return proofGenerator
          .initProgressMeter(progressCallback, progressPercentMinCheckInterval)
          .generate(ciphertext, witness, elements, index, data);
    }
  };

  /**
   * Verifies a Schnorr proof.
   *
   * @param exponentiatedElement
   *            The exponentiated element. This should be an element of the
   *            specified group.
   * @param voterID
   *            The voter IS. This should be a string.
   * @param electionID
   *            The election event ID. This should be a string.
   * @param proof
   *            The proof that is to be verified.
   * @param group
   *            The mathematical group. This should be a Zp subgroup.
   * @param progressCallback
   *            Progress callback function (optional).
   * @param progressPercentMinCheckInterval
   *            Progress percentage minimum check interval (optional).
   *
   * @returns true if the Schnorr Proof can be verified, false otherwise.
   */
  box.proofs.service.verifySchnorrProof = function(
      element, voterID, electionEventID, proof, group, progressCallback,
      progressPercentMinCheckInterval) {

    return proofsFactory
        .createSchnorrProofVerifier(
            element, voterID, electionEventID, proof, group)
        .initProgressMeter(progressCallback, progressPercentMinCheckInterval)
        .verify();
  };

  /**
   * Verifies an exponentiation proof.
   *
   * @param exponentiatedElements
   *            The exponentiated elements. These should consist of the
   *            following base elements, exponentiated with the specified
   *            exponent.
   * @param baseElements
   *            The base elements used for the exponentiations. These should
   *            correspond to elements of the specified group.
   * @param proof
   *            The proof that is to be verified.
   * @param group
   *            The mathematical group. This should be a Zp subgroup.
   * @param progressCallback
   *            Progress callback function (optional).
   * @param progressPercentMinCheckInterval
   *            Progress percentage minimum check interval (optional).
   *
   * @returns true if the exponentiation proof can be verified, false
   *          otherwise.
   */
  box.proofs.service.verifyExponentiationProof = function(
      exponentiatedElements, baseElements, proof, group, progressCallback,
      progressPercentMinCheckInterval) {

    return proofsFactory
        .createExponentiationProofVerifier(
            exponentiatedElements, baseElements, proof, group)
        .initProgressMeter(progressCallback, progressPercentMinCheckInterval)
        .verify();
  };

  /**
   * Verifies a plaintext proof.
   *
   * @param publicKey
   *            The primary public key. This should be an ElGamal public key.
   * @param ciphertext
   *            The ciphertext. This should be the output from ElGamal
   *            encryption.
   * @param plaintext
   *            The plaintext. This should be an array of elements of the
   *            received group.
   * @param proof
   *            The proof that is to be verified.
   * @param group
   *            The mathematical group. This should be a Zp subgroup.
   * @param progressCallback
   *            Progress callback function (optional).
   * @param progressPercentMinCheckInterval
   *            Progress percentage minimum check interval (optional).
   * @returns true if the plaintext proof can be verified, false otherwise.
   */
  box.proofs.service.verifyPlaintextProof = function(
      publicKey, ciphertext, plaintext, proof, group, progressCallback,
      progressPercentMinCheckInterval) {

    return proofsFactory.createPlaintextProofVerifier(publicKey, group)
        .initProgressMeter(progressCallback, progressPercentMinCheckInterval)
        .verify(ciphertext, plaintext, proof);
  };

  /**
   * Verifies a simple plaintext equality proof.
   *
   * @param primaryCiphertext
   *            The primary ciphertext. This should be the output from ElGamal
   *            encryption.
   * @param primaryPublicKey
   *            The primary public key. This should be an ElGamal public key.
   * @param secondaryCiphertext
   *            The secondary ciphertext. This should be the output from
   *            ElGamal encryption.
   * @param secondaryPublicKey
   *            The secondary public key. This should be an ElGamal public
   *            key.
   * @param proof
   *            The proof that is to be verified.
   * @param group
   *            The mathematical group. This should be a Zp subgroup.
   * @param progressCallback
   *            Progress callback function (optional).
   * @param progressPercentMinCheckInterval
   *            Progress percentage minimum check interval (optional).
   *
   * @returns true if the simple plaintext equality proof can be verified,
   *          false otherwise.
   */
  box.proofs.service.verifySimplePlaintextEqualityProof = function(
      primaryCiphertext, primaryPublicKey, secondaryCiphertext,
      secondaryPublicKey, proof, group, progressCallback,
      progressPercentMinCheckInterval) {

    return proofsFactory
        .createSimplePlaintextEqualityProofVerifier(
            primaryCiphertext, primaryPublicKey, secondaryCiphertext,
            secondaryPublicKey, proof, group)
        .initProgressMeter(progressCallback, progressPercentMinCheckInterval)
        .verify();
  };

  /**
   * Verifies a plaintext equality proof.
   *
   * @param primaryCiphertext
   *            The primary ciphertext. This should be the output from ElGamal
   *            encryption.
   * @param primaryPublicKey
   *            The primary public key. This should be an ElGamal public key.
   * @param secondaryCiphertext
   *            The secondary ciphertext. This should be the output from
   *            ElGamal encryption.
   * @param secondaryPublicKey
   *            The secondary public key. This should be an ElGamal public
   *            key.
   * @param proof
   *            The proof that is to be verified.
   * @param group
   *            The mathematical group. This should be a Zp subgroup.
   * @param progressCallback
   *            Progress callback function (optional).
   * @param progressPercentMinCheckInterval
   *            Progress percentage minimum check interval (optional).
   *
   * @returns true if the plaintext equality proof can be verified, false
   *          otherwise.
   */
  box.proofs.service.verifyPlaintextEqualityProof = function(
      primaryCiphertext, primaryPublicKey, secondaryCiphertext,
      secondaryPublicKey, proof, group, progressCallback,
      progressPercentMinCheckInterval) {

    return proofsFactory
        .createPlaintextEqualityProofVerifier(
            primaryCiphertext, primaryPublicKey, secondaryCiphertext,
            secondaryPublicKey, proof, group)
        .initProgressMeter(progressCallback, progressPercentMinCheckInterval)
        .verify();
  };

  /**
   * Verifies a plaintext exponent equality proof.
   *
   * @param ciphertext
   *            The ciphertext. This should be the output from ElGamal
   *            encryption.
   * @param baseElements
   *            The array of base elements. All of these elements should be
   *            members of the received group.
   * @param witness1
   *            The primary witness. This should be an Exponent.
   * @param witness2
   *            The secondary witness. This should be an Exponent.
   * @param proof
   *            The proof that is to be verified.
   * @param group
   *            The mathematical group. This should be a Zp subgroup.
   * @param progressCallback
   *            Progress callback function (optional).
   * @param progressPercentMinCheckInterval
   *            Progress percentage minimum check interval (optional).
   *
   * @returns true if the plaintext exponent equality proof can be verified,
   *          false otherwise.
   */
  box.proofs.service.verifyPlaintextExponentEqualityProof = function(
      ciphertext, baseElements, proof, group, progressCallback,
      progressPercentMinCheckInterval) {

    return proofsFactory
        .createPlaintextExponentEqualityProofVerifier(
            ciphertext, baseElements, proof, group)
        .initProgressMeter(progressCallback, progressPercentMinCheckInterval)
        .verify();
  };

  /**
   * Verifies an OR-proof.
   *
   * @param publicKey
   *            The public key. This should be an ElGamal public key.
   * @param ciphertext
   *            The ciphertext of the mathematical group element that was
   *            encrypted. This should be the output from ElGamal encryption.
   * @param elements
   *            The mathematical group elements, one of which was encrypted.
   *            This should be an array of elements of the received group.
   * @param data
   *            Any extra data used for the proof generation. This should be a
   *            String. NOTE: If this parameter is null, then data will be set
   *            to empty string.
   * @param proof
   *            The proof that is to be verified.
   * @param group
   *            The mathematical group. This should be a Zp subgroup.
   * @param progressCallback
   *            Progress callback function (optional).
   * @param progressPercentMinCheckInterval
   *            Progress percentage minimum check interval (optional).
   * @returns true if the OR-proof can be verified, false otherwise.
   */
  box.proofs.service.verifyORProof = function(
      publicKey, ciphertext, elements, data, proof, group, progressCallback,
      progressPercentMinCheckInterval) {

    return proofsFactory
        .createORProofVerifier(publicKey, elements.length, group)
        .initProgressMeter(progressCallback, progressPercentMinCheckInterval)
        .verify(ciphertext, elements, data, proof);
  };

  /**
   * Pre-computes a Schnorr proof. IMPORTANT: The same pre-computed values
   * must not be used twice.
   *
   * @param voterID
   *            The voter ID. This should be a string.
   * @param electionID
   *            The election event ID. This should be a string.
   * @param group
   *            The mathematical group. This should be a Zp subgroup.
   * @param progressCallback
   *            Progress callback function or pre-computed values (optional).
   * @param progressPercentMinCheckInterval
   *            Progress percentage minimum check interval, if the previous
   *            parameter is a progressCallback function (optional).
   *
   * @returns The Schnorr proof pre-computed values.
   */
  box.proofs.service.preComputeSchnorrProof = function(
      voterID, electionID, group, progressCallbackOrPreComputedValues,
      progressPercentMinCheckInterval) {

    return proofsFactory.createSchnorrProofGenerator(voterID, electionID, group)
        .initProgressMeter(
            progressCallbackOrPreComputedValues,
            progressPercentMinCheckInterval)
        .preCompute();
  };

  /**
   * Pre-computes an exponentiation proof. IMPORTANT: The same pre-computed
   * values must not be used twice.
   *
   * @param baseElements
   *            The base elements used for the exponentiations. These should
   *            correspond to elements of the specified group.
   * @param group
   *            The mathematical group. This should be a Zp subgroup.
   * @param progressCallback
   *            Progress callback function or pre-computed values (optional).
   * @param progressPercentMinCheckInterval
   *            Progress percentage minimum check interval, if the previous
   *            parameter is a progressCallback function (optional).
   *
   * @returns The exponentiation proof pre-computed values.
   */
  box.proofs.service.preComputeExponentiationProof = function(
      baseElements, group, progressCallbackOrPreComputedValues,
      progressPercentMinCheckInterval) {

    return proofsFactory.createExponentiationProofGenerator(baseElements, group)
        .initProgressMeter(
            progressCallbackOrPreComputedValues,
            progressPercentMinCheckInterval)
        .preCompute();
  };

  /**
   * Pre-computes a plaintext proof. IMPORTANT: The same pre-computed values
   * must not be used twice.
   *
   * @param publicKey
   *            The primary public key. This should be an ElGamal public key.
   * @param group
   *            The mathematical group. This should be a Zp subgroup.
   * @param progressCallback
   *            Progress callback function or pre-computed values (optional).
   * @param progressPercentMinCheckInterval
   *            Progress percentage minimum check interval, if the previous
   *            parameter is a progressCallback function (optional).
   *
   * @returns The plaintext proof pre-computed values.
   */
  box.proofs.service.preComputePlaintextProof = function(
      publicKey, group, progressCallbackOrPreComputedValues,
      progressPercentMinCheckInterval) {

    return proofsFactory.createPlaintextProofGenerator(publicKey, group)
        .initProgressMeter(
            progressCallbackOrPreComputedValues,
            progressPercentMinCheckInterval)
        .preCompute();
  };

  /**
   * Pre-computes values for a simple plaintext equality proof. IMPORTANT: The
   * same pre-computed values must not be used twice.
   *
   * @param primaryPublicKey
   *            The primary public key. This should be an ElGamal public key.
   * @param secondaryPublicKey
   *            The secondary public key. This should be an ElGamal public
   *            key.
   * @param group
   *            The mathematical group. This should be a Zp subgroup.
   * @param progressCallback
   *            Progress callback function or pre-computed values (optional).
   * @param progressPercentMinCheckInterval
   *            Progress percentage minimum check interval, if the previous
   *            parameter is a progressCallback function (optional).
   *
   * @returns The simple plaintext equality proof pre-computed values.
   */
  box.proofs.service.preComputeSimplePlaintextEqualityProof = function(
      primaryPublicKey, secondaryPublicKey, group,
      progressCallbackOrPreComputedValues, progressPercentMinCheckInterval) {

    return proofsFactory
        .createSimplePlaintextEqualityProofGenerator(
            primaryPublicKey, secondaryPublicKey, group)
        .initProgressMeter(
            progressCallbackOrPreComputedValues,
            progressPercentMinCheckInterval)
        .preCompute();
  };

  /**
   * Pre-computes values for a plaintext equality proof. IMPORTANT: The same
   * pre-computed values must not be used twice.
   *
   * @param primaryPublicKey
   *            The primary public key. This should be an ElGamal public key.
   * @param secondaryPublicKey
   *            The secondary public key. This should be an ElGamal public
   *            key.
   * @param group
   *            The mathematical group. This should be a Zp subgroup.
   * @param progressCallback
   *            Progress callback function or pre-computed values (optional).
   * @param progressPercentMinCheckInterval
   *            Progress percentage minimum check interval, if the previous
   *            parameter is a progressCallback function (optional).
   *
   * @returns The plaintext equality proof pre-computed values.
   */
  box.proofs.service.preComputePlaintextEqualityProof = function(
      primaryPublicKey, secondaryPublicKey, group,
      progressCallbackOrPreComputedValues, progressPercentMinCheckInterval) {

    return proofsFactory
        .createPlaintextEqualityProofGenerator(
            primaryPublicKey, secondaryPublicKey, group)
        .initProgressMeter(
            progressCallbackOrPreComputedValues,
            progressPercentMinCheckInterval)
        .preCompute();
  };

  /**
   * Returns pre-computed values for a plaintext exponent equality proof.
   * IMPORTANT: The same pre-computed values must not be used twice.
   *
   * @param plaintext
   *            The plaintext. It should be an array of members of the
   *            received group.
   * @param group
   *            The mathematical group. This should be a Zp subgroup.
   * @param progressCallback
   *            Progress callback function or pre-computed values (optional).
   * @param progressPercentMinCheckInterval
   *            Progress percentage minimum check interval, if the previous
   *            parameter is a progressCallback function (optional).
   *
   * @returns The plaintext exponent equality proof pre-computed values.
   */
  box.proofs.service.preComputePlaintextExponentEqualityProof = function(
      plaintext, group, progressCallbackOrPreComputedValues,
      progressPercentMinCheckInterval) {

    return proofsFactory
        .createPlaintextExponentEqualityProofGenerator(plaintext, group)
        .initProgressMeter(
            progressCallbackOrPreComputedValues,
            progressPercentMinCheckInterval)
        .preCompute();
  };

  /**
   * Pre-computes an OR-proof. IMPORTANT: The same pre-computed values must
   * not be used twice.
   *
   * @param publicKey
   *            The public key. This should be an ElGamal public key.
   * @param numElements
   *            The number of elements belonging to the received mathematical
   *            group that will be used for the proof, one of which will be
   *            encrypted.
   * @param group
   *            The mathematical group. This should be a Zp subgroup.
   * @param progressCallback
   *            Progress callback function or pre-computed values (optional).
   * @param progressPercentMinCheckInterval
   *            Progress percentage minimum check interval, if the previous
   *            parameter is a progressCallback function (optional).
   *
   * @returns The OR-proof pre-computed values.
   */
  box.proofs.service.preComputeORProof = function(
      publicKey, numElements, group, progressCallbackOrPreComputedValues,
      progressPercentMinCheckInterval) {

    return proofsFactory.createORProofGenerator(publicKey, numElements, group)
        .initProgressMeter(
            progressCallbackOrPreComputedValues,
            progressPercentMinCheckInterval)
        .preCompute();
  };

  /**
   * Utility method that checks if the argument is instance of type
   * ProofPreComputedValues.
   *
   * @param values
   *            argument to check.
   * @returns {boolean} true if values is instance of ProofPreComputedValues.
   */
  function isPreComputedValues(values) {
    return !!(values) && values.className === 'ProofPreComputedValues';
  }
};

/*
 * 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
 */
cryptolib.modules.stores = cryptolib.modules.stores || {};

/**
 * Defines a pkcs12 store.
 */
cryptolib.modules.stores.pkcs12 = function(box) {
  'use strict';

  box.stores = box.stores || {};
  box.stores.pkcs12 = {};

  var converters;
  var exceptions;

  cryptolib('commons', function(box) {
    converters = new box.commons.utils.Converters();
    exceptions = box.commons.exceptions;
  });

  /**
   * Defines a PKCS12.
   *
   * @param pkcs12DerB64
   *            PKCS12, as string in Base64 encoded DER format.
   */
  box.stores.pkcs12.Pkcs12 = function(pkcs12DerB64) {
    this.pkcs12Der = converters.base64Decode(pkcs12DerB64);
    this.pkcs12Asn1 = forge.asn1.fromDer(this.pkcs12Der, false);
  };

  box.stores.pkcs12.Pkcs12.prototype = {

    /**
     * Retrieves the private key of the PKCS12.
     *
     * @param passwordB64
     *            Password as string in Base64 encoded format.
     * @return Private key of PKCS12, as string in PEM format.
     */
    getPrivateKey: function(passwordB64) {
      var aliases = Array.prototype.slice.call(arguments, 1);

      function isAliasInList(alias) {
        var returnValue = false;

        if (aliases.indexOf(alias) >= 0) {
          returnValue = true;
        }

        return returnValue;
      }

      var privateKeyAlias = '';
      try {
        var password = converters.base64Decode(passwordB64);

        var pkcs12 =
            forge.pkcs12.pkcs12FromAsn1(this.pkcs12Asn1, false, password);

        // Retrieve private key safe bag from PKCS12.
        var privateKeys = {};
        for (var i = 0; i < pkcs12.safeContents.length; i++) {
          var safeContents = pkcs12.safeContents[i];
          for (var j = 0; j < safeContents.safeBags.length; j++) {
            var safeBag = safeContents.safeBags[j];
            if (safeBag.type === forge.pki.oids.pkcs8ShroudedKeyBag) {
              privateKeyAlias = safeBag.attributes.friendlyName[0];
              if (aliases.length === 0 || isAliasInList(privateKeyAlias)) {
                privateKeys[privateKeyAlias] =
                    forge.pki.privateKeyToPem(safeBag.key);
              }
            }
          }
        }

        // Check whether any private key safe bags were found.
        if (Object.getOwnPropertyNames(privateKeys).length === 0) {
          throw new exceptions.CryptoLibException(
              'Could not find any private key safe bags in PKCS12.');
        }

        return privateKeys;
      } catch (error) {
        var errorMsg = 'Could not retrieve private key from PKCS12.';
        if (privateKeyAlias.length > 0) {
          errorMsg = 'Could not retrieve private key with alias ' +
              privateKeyAlias + ' from PKCS12.';
        }

        throw new exceptions.CryptoLibException(errorMsg);
      }
    },

    /**
     * Retrieves the private key chain from the PKCS12.
     *
     * @param passwordB64
     *            Password as string in Base64 encoded format.
     * @return Private key chain of PKCS12, as PrivateKeyChain object.
     */
    getPrivateKeyChain: function(passwordB64) {
      try {
        var password = converters.base64Decode(passwordB64);

        var pkcs12 =
            forge.pkcs12.pkcs12FromAsn1(this.pkcs12Asn1, false, password);

        // Retrieve private key safe bags from PKCS12.
        var privateKeyChain = new box.stores.pkcs12.PrivateKeyChain();
        for (var i = 0; i < pkcs12.safeContents.length; i++) {
          var safeContents = pkcs12.safeContents[i];
          for (var j = 0; j < safeContents.safeBags.length; j++) {
            var safeBag = safeContents.safeBags[j];

            if (safeBag.type === forge.pki.oids.pkcs8ShroudedKeyBag) {
              var alias = safeBag.attributes.friendlyName[0];
              var privateKeyPem = forge.pki.privateKeyToPem(safeBag.key);
              privateKeyChain.add(alias, privateKeyPem);
            }
          }
        }

        // Check whether any private key safe bags were found.
        if (privateKeyChain.length === 0) {
          throw new exceptions.CryptoLibException(
              'Could not find any private key safe bags in PKCS12.');
        }

        return privateKeyChain;
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'Could not retrieve private key chain from PKCS12.', error);
      }
    },

    /**
     * Retrieves the certificate chain of the PKCS12.
     *
     * @param passwordB64
     *            Password as string in Base64 encoded format.
     * @return Certificate chain of PKCS12, as array of strings in PEM
     *         format.
     */
    getCertificateChain: function(passwordB64) {
      try {
        var password = converters.base64Decode(passwordB64);

        var pkcs12 =
            forge.pkcs12.pkcs12FromAsn1(this.pkcs12Asn1, false, password);

        // Retrieve certificate safe bags from PKCS12.
        var certificatePemChain = [];
        for (var i = 0; i < pkcs12.safeContents.length; i++) {
          var safeContents = pkcs12.safeContents[i];
          for (var j = 0; j < safeContents.safeBags.length; j++) {
            var safeBag = safeContents.safeBags[j];

            if (safeBag.type === forge.pki.oids.certBag) {
              var certificatePem = forge.pki.certificateToPem(safeBag.cert);
              certificatePemChain.push(certificatePem);
            }
          }
        }

        // Check whether any certificate safe bags were found.
        if (certificatePemChain.length === 0) {
          throw new exceptions.CryptoLibException(
              'Could not find any certificate safe bags in PKCS12.');
        }

        return certificatePemChain;
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'Could not retrieve certificate chain from PKCS12.', error);
      }
    },

    /**
     * Retrieves the certificate chain of the PKCS12 filtered by alias.
     *
     * @param passwordB64
     *            Password as string in Base64 encoded format.
     * @param alias
     *            Alias to filter
     * @return Certificate chain of PKCS12, as array of strings in PEM
     *         format.
     */
    getCertificateChainByAlias: function(passwordB64, alias) {
      try {
        var password = converters.base64Decode(passwordB64);

        var pkcs12 =
            forge.pkcs12.pkcs12FromAsn1(this.pkcs12Asn1, false, password);

        var discardedBags = {};
        var nextBagId = null;

        // Retrieve certificate safe bags from PKCS12.
        var certificatePemChain = [];
        for (var i = 0; i < pkcs12.safeContents.length; i++) {
          var safeContents = pkcs12.safeContents[i];
          for (var j = 0; j < safeContents.safeBags.length; j++) {
            var safeBag = safeContents.safeBags[j];
            if (safeBag.type === forge.pki.oids.certBag) {
              var bagFound = null;

              // if not initializated the chain, find the one with alias
              // or if is the one that goes next into the list take it
              if ((!nextBagId && safeBag.attributes &&
                  safeBag.attributes.friendlyName &&
                  safeBag.attributes.friendlyName[0] === alias) ||
                  (nextBagId && nextBagId === safeBag.cert.subject.hash)) {
                bagFound = safeBag;
              }
              // otherwise, if not initializated and it's not the alias, save it
              // for later use
              else {
                discardedBags[safeBag.cert.subject.hash] = safeBag;
              }

              // add the bag and check if we have the next one in the list we
              // had
              while (bagFound) {
                // transform the current to PEM and save it
                var certificatePem = forge.pki.certificateToPem(bagFound.cert);
                certificatePemChain.push(certificatePem);

                // we arrived to the root so we are done!
                if (bagFound.cert.subject.hash === bagFound.cert.issuer.hash) {
                  return certificatePemChain;
                }

                // save which is the next bag we need
                nextBagId = bagFound.cert.issuer.hash;
                // go for the next in the list of discarded ones
                bagFound = discardedBags[nextBagId];
              }
            }
          }
        }

        // Check whether any certificate safe bags were found.
        if (certificatePemChain.length === 0) {
          throw new exceptions.CryptoLibException(
              'Could not find any certificates for that alias.');
        }

        return certificatePemChain;

      } catch (error) {
        throw new exceptions.CryptoLibException(
            'Could not retrieve certificate chain from PKCS12.', error);
      }
    },

    /**
     * Retrieves an issuer certificate from the certificate chain of the
     * PKCS12, given the issuer common name.
     *
     * @param passwordB64
     *            Password as string in Base64 encoded format.
     * @param issuerCn
     *            Issuer common name of certificate to retrieve, as string.
     * @return Certificate with given issuer common name, as string in PEM
     *         format.
     */
    getCertificateByIssuer: function(passwordB64, issuerCn) {
      try {
        var password = converters.base64Decode(passwordB64);

        var pkcs12 =
            forge.pkcs12.pkcs12FromAsn1(this.pkcs12Asn1, false, password);

        // Retrieve certificate from PKCS12.
        var certificate = null;
        var certificateFound = false;
        for (var i = 0; i < pkcs12.safeContents.length; i++) {
          var safeContents = pkcs12.safeContents[i];
          for (var j = 0; j < safeContents.safeBags.length; j++) {
            var safeBag = safeContents.safeBags[j];

            if (safeBag.type === forge.pki.oids.certBag &&
                safeBag.cert.issuer.getField('CN').value === issuerCn) {
                certificate = safeBag.cert;
                certificateFound = true;
                break;
            }
          }
          if (certificateFound) {
            break;
          }
        }

        // Check whether certificate was found.
        if (certificate === null) {
          throw new exceptions.CryptoLibException(
              'Could not find certificate in PKCS12 with issuer common name ' +
              issuerCn);
        }

        return forge.pki.certificateToPem(certificate);
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'Could not retrieve certificate with issuer common name ' +
                issuerCn + ' from PKCS12.',
            error);
      }
    },

    /**
     * Retrieves the subject certificate from the certificate chain of the
     * PKCS12, given the subject common name.
     *
     * @param passwordB64
     *            Password as string in Base64 encoded format.
     * @param subjectCn
     *            Subject common name of certificate to retrieve, as string.
     * @return Certificate with given issuer common name, as string in PEM
     *         format.
     */
    getCertificateBySubject: function(passwordB64, subjectCn) {
      try {
        var password = converters.base64Decode(passwordB64);

        var pkcs12 =
            forge.pkcs12.pkcs12FromAsn1(this.pkcs12Asn1, false, password);

        // Retrieve subject certificate from PKCS12.
        var certificate = null;
        var certificateFound = false;
        for (var i = 0; i < pkcs12.safeContents.length; i++) {
          var safeContents = pkcs12.safeContents[i];
          for (var j = 0; j < safeContents.safeBags.length; j++) {
            var safeBag = safeContents.safeBags[j];

            if (safeBag.type === forge.pki.oids.certBag &&
                safeBag.cert.subject.getField('CN').value === subjectCn) {
                certificate = safeBag.cert;
                certificateFound = true;
                break;
            }
          }
          if (certificateFound) {
            break;
          }
        }

        // Check whether certificate was found.
        if (certificate === null) {
          throw new exceptions.CryptoLibException(
              'Could not find certificate in PKCS12 with subject common name ' +
              subjectCn);
        }

        return forge.pki.certificateToPem(certificate);
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'Could not retrieve certificate with subject common name ' +
                subjectCn + ' from PKCS12.',
            error);
      }
    },

    /**
     * Retrieves the PKCS12 as a Base64 encoded DER string.
     *
     * @return PKCS12, as Base64 encoded DER string.
     */
    getBase64Encoded: function() {
      return converters.base64Encode(this.pkcs12Der);
    }
  };

  /**
   * Container class for a chain of private keys to be inserted in a key
   * store.
   */
  box.stores.pkcs12.PrivateKeyChain = function() {
    this.map = {};
  };

  box.stores.pkcs12.PrivateKeyChain.prototype = {

    /**
     * Adds a private key to the chain.
     *
     * @param alias
     *            Alias of private key, as string.
     * @param privateKeyPem
     *            Private key to add, as string in PEM format.
     */
    add: function(alias, privateKeyPem) {
      this.map[alias] = privateKeyPem;
    },

    /**
     * Retrieves a private key from the chain.
     *
     * @return Private key chain, as array of strings in PEM format.
     */
    get: function(alias) {
      return this.map[alias];
    }
  };
};

/*
 * 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
 */
cryptolib.modules.stores = cryptolib.modules.stores || {};

/**
 * A module that provides stores services.
 * <P>
 */
cryptolib.modules.stores.service = function(box) {
  'use strict';

  box.stores = box.stores || {};
  box.stores.service = {};

  var sks;

  cryptolib('stores.sks', function(box) {
    sks = box.stores.sks;
  });

  /**
   * @static
   *
   * @returns a Sks object.
   */
  box.stores.service.loadStore = function(store) {
    return new sks.SksReader(store);
  };
};

/*
 * 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
 */
/**
 * Scytl Key Store. It is a custom keystore.
 */
cryptolib.modules.stores.sks = function(box) {
  'use strict';

  box.stores = box.stores || {};
  box.stores.sks = {};

  var converters;
  var secretKeyGeneratorFactory;
  var cipher;
  var pkcs12Module;
  var exceptions;
  var homomorphicKeyFactory;

  var f = function(box) {
    converters = new box.commons.utils.Converters();
    cipher = (new box.symmetric.cipher.factory.SymmetricCipherFactory())
                 .getCryptoSymmetricCipher();
    secretKeyGeneratorFactory =
        new box.primitives.derivation.factory.SecretKeyGeneratorFactory();
    pkcs12Module = box.stores.pkcs12;
    exceptions = box.commons.exceptions;
    homomorphicKeyFactory = new box.homomorphic.keypair.factory.KeyFactory();
  };

  f.policies = {
    symmetric: {
      secretkey: {
        encryption:
            {length: box.policies.symmetric.secretkey.encryption.length},
        mac: {length: box.policies.symmetric.secretkey.mac.length},
        secureRandom:
            {provider: box.policies.symmetric.secretkey.secureRandom.provider}
      },
      cipher: {
        provider: box.policies.symmetric.cipher.provider,
        algorithm: box.policies.symmetric.cipher.algorithm,
        initializationVectorLengthBytes:
            box.policies.symmetric.cipher.initializationVectorLengthBytes,
        secureRandom:
            {provider: box.policies.symmetric.cipher.secureRandom.provider}
      }
    },
    primitives: {
      derivation: {
        pbkdf: {
          provider: box.policies.primitives.derivation.pbkdf.provider,
          hash: box.policies.primitives.derivation.pbkdf.hash,
          saltLengthBytes:
              box.policies.primitives.derivation.pbkdf.saltLengthBytes,
          keyLengthBytes:
              box.policies.primitives.derivation.pbkdf.keyLengthBytes,
          iterations: box.policies.primitives.derivation.pbkdf.iterations
        }
      }
    }
  };

  cryptolib(
      'commons', 'primitives.derivation', 'symmetric.cipher', 'stores.pkcs12',
      'homomorphic.keypair', f);

  /**
   * Defines a SksReader.
   *
   * @param data.
   */
  box.stores.sks.SksReader = function(data) {
    // A cache of keys derived from store passwords, to prevent consecutive
    // operations on a keystore to derive keys each time.
    var bigintKeys = {};
    var derivedKeys = {};

    // Create the PBKDF generator.
    var cryptoPbkdfSecretKeyGenerator = secretKeyGeneratorFactory.createPBKDF();

    try {
      var loadKeyStore = function(data) {
        var keyStore;
        if (data) {
          if (typeof data === 'string') {
            keyStore = JSON.parse(data);
          } else if (typeof data === 'object') {
            keyStore = data;
          }
        }

        return keyStore;
      };

      var validateKeyStoreStructure = function(keyStore) {
        if (typeof keyStore.salt === 'undefined') {
          throw new Error('Missing salt');
        }

        if (typeof keyStore.store === 'undefined') {
          throw new Error('Missing store');
        }
      };

      this.keyStore = loadKeyStore(data);
      validateKeyStoreStructure(this.keyStore);

      this.store = new pkcs12Module.Pkcs12(this.keyStore.store);

    } catch (error) {
      throw new Error('Invalid keyStore: ' + error.message);
    }

    function validateEncryptedKey(encryptedKey, keyType) {
      if (!encryptedKey) {
        throw new exceptions.CryptoLibException(
            'The keyStore does not contain a key of type ' + keyType);
      }
    }

    /**
     * Derive a key from the keystore password.
     *
     * @param {string} passwordB64 the Base64-encoded keystore password
     * @returns the Base64-encoded key derived from the password
     */
    this._deriveKey = function(passwordB64) {
      return cryptoPbkdfSecretKeyGenerator.generateSecret(
          passwordB64, this.keyStore.salt);
    };

    /**
     * Get the Base64-encoded key derived from the store password.
     *
     * @param {string} passwordB64 the Base64-encoded store password
     * @return the Base64-encoded key derived from the store password
     */
    this._getDerivedKey = function(passwordB64) {
      if (!derivedKeys.hasOwnProperty(passwordB64)) {
        derivedKeys[passwordB64] = this._deriveKey(passwordB64);
      }

      return derivedKeys[passwordB64];
    };

    /**
     * Get the Base64-encoded BigInteger representation of the key derived from
     * the store password.
     *
     * @param {string} passwordB64 the Base64-encoded store password
     * @returns the Base64-encoded BigInteger representation of the key derived
     * from the store password
     */
    this._getBigIntKey = function(passwordB64) {
      if (!bigintKeys.hasOwnProperty(passwordB64)) {
        var derivedPasswordBase64 = this._getDerivedKey(passwordB64);

        var derivedPassword = converters.bytesFromString(
            converters.base64Decode(derivedPasswordBase64));
        var derivedPasswordString =
            new box.forge.jsbn.BigInteger(derivedPassword);
        var longPasswordB64 =
            converters.base64Encode(derivedPasswordString.toString(36));

        bigintKeys[passwordB64] = longPasswordB64;
      }

      return bigintKeys[passwordB64];
    };

    this._getKey = function(alias, encryptedKey, passwordB64, keyType) {

      validateEncryptedKey(encryptedKey, keyType);

      var aliasAndKeyBase64 = this.decryptKey(encryptedKey, passwordB64);

      var aliasAndKey = converters.base64Decode(aliasAndKeyBase64);

      var decryptedAlias = aliasAndKey.slice(0, alias.length);

      if (decryptedAlias !== alias) {
        throw new exceptions.CryptoLibException(
            'The decrypted ' + keyType +
            ' key does not correspond to the given alias.');
      }

      var key = aliasAndKey.slice(alias.length, aliasAndKey.length);
      var keyBase64 = converters.base64Encode(key);
      return keyBase64;
    };
  };

  box.stores.sks.SksReader.prototype = {
    getPrivateKeys: function(passwordB64) {
      var updateArguments = function(bigIntKey, args) {
        var argumentsCopy = Array.prototype.slice.call(args, 1);
        argumentsCopy.unshift(bigIntKey);

        return argumentsCopy;
      };

      return this.store.getPrivateKey.apply(
          this.store,
          updateArguments(this._getBigIntKey(passwordB64), arguments));
    },

    getSecretKey: function(alias, passwordB64) {
      var encryptedKey = this.keyStore.secrets[alias];
      return this._getKey(alias, encryptedKey, passwordB64, 'secret');
    },

    getElGamalPrivateKey: function(alias, passwordB64) {
      var encryptedKey = this.keyStore.egPrivKeys[alias];
      var keyBase64 =
          this._getKey(alias, encryptedKey, passwordB64, 'ElGamal private');
      return homomorphicKeyFactory.createPrivateKey(keyBase64);
    },

    decryptKey: function(encryptedKey, passwordB64) {
      return cipher.decrypt(this._getDerivedKey(passwordB64), encryptedKey);
    },

    /**
     * Retrieves the private key chain
     *
     * @param passwordB64
     *            Password as string in Base64 encoded format.
     * @return Private key chain of as PrivateKeyChain object, which
     *         contains aliases and their related private keys in PEM
     *         format.
     */
    getPrivateKeyChain: function(passwordB64) {
      return this.store.getPrivateKeyChain(this._getBigIntKey(passwordB64));
    },

    /**
     * Retrieves the full certificate chain.
     *
     * @param passwordB64
     *            Password as string in Base64 encoded format.
     * @return Certificate chain as array of strings in PEM format.
     */
    getCertificateChain: function(passwordB64) {
      return this.store.getCertificateChain(this._getBigIntKey(passwordB64));
    },

    /**
     * Retrieves a certificate chain by private key alias
     *
     * @param passwordB64
     *            Password as string in Base64 encoded format.
     * @param alias
     *            Alias to filter
     * @return Certificate chain as array of strings in PEM format.
     */
    getCertificateChainByAlias: function(passwordB64, alias) {
      return this.store.getCertificateChainByAlias(
          this._getBigIntKey(passwordB64), alias);
    },

    /**
     * Retrieves a certificate by subject common name.
     *
     * @param passwordB64
     *            Password as string in Base64 encoded format.
     * @param subjectCn
     *            Subject common name of certificate to retrieve, as string.
     * @return Certificate with given subject common name, as string in PEM
     *         format.
     */
    getCertificateBySubject: function(passwordB64, subjectCn) {
      return this.store.getCertificateBySubject(
          this._getBigIntKey(passwordB64), subjectCn);
    }
  };
};

/*
 * 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
 */
cryptolib.modules.symmetric = cryptolib.modules.symmetric || {};

/**
 * Defines the symmetric ciphers.
 *
 * @namespace symmetric/cipher
 */
cryptolib.modules.symmetric.cipher = function(box) {
  'use strict';

  box.symmetric = box.symmetric || {};
  box.symmetric.cipher = {};

  /**
   * Allows a symmetric cipher factory to be obtained.
   *
   * @exports symmetric/cipher/factory
   */
  box.symmetric.cipher.factory = {};

  var policies = {
    cipher: {
      algorithm: box.policies.symmetric.cipher.algorithm,
      provider: box.policies.symmetric.cipher.provider,
      initializationVectorLengthBytes:
          box.policies.symmetric.cipher.initializationVectorLengthBytes
    }
  };

  for (var prop in policies.cipher.algorithm) {
    if (typeof prop !== 'undefined') {
      policies.cipher.algorithm = policies.cipher.algorithm[prop];
    }
  }

  var utils, exceptions, randomFactory;

  var f = function(box) {
    utils = box.commons.utils;
    exceptions = box.commons.exceptions;
    randomFactory =
        new box.primitives.securerandom.factory.SecureRandomFactory();
  };

  f.policies = {
    primitives: {
      secureRandom:
          {provider: box.policies.symmetric.cipher.secureRandom.provider}
    }
  };

  cryptolib('commons', 'primitives.securerandom', f);

  /**
   * Factory class that allows a symmetric cipher to be obtained.
   *
   * @class
   */
  box.symmetric.cipher.factory.SymmetricCipherFactory = function() {};

  box.symmetric.cipher.factory.SymmetricCipherFactory.prototype = {

    /**
     * Create a new symmetric cipher.
     * <p>
     * The particular cipher that is returned depends on the policies.
     *
     * @function
     * @returns a new symmetric cipher.
     */
    getCryptoSymmetricCipher: function() {

      var secureRandom;

      try {
        if (policies.cipher.provider ===
            Config.symmetric.cipher.provider.FORGE) {
          secureRandom = randomFactory.getCryptoRandomBytes();

          return this.getCryptoForgeSymmetricCipher(secureRandom);
        } else {
          throw new exceptions.CryptoLibException(
              'No suitable provider was provided.');
        }
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'A CryptoSymmetricCipher could not be obtained.', error);
      }
    },

    /**
     * Create a new CryptoForgeSymmetricCipher.
     *
     * @function
     * @param secureRandom
     *            {CryptoScytlRandomBytes} a secure source of random bytes.
     * @returns {symmetric/cipher.CryptoForgeSymmetricCipher} a new
     *          CryptoForgeSymmetricCipher.
     */
    getCryptoForgeSymmetricCipher: function(secureRandom) {
      return new CryptoForgeSymmetricCipher(secureRandom);
    }
  };

  /**
   * @class
   * @param randomGenerator
   *            {CryptoScytlRandomBytes} a secure random bytes generator.
   * @memberof symmetric/cipher
   */
  function CryptoForgeSymmetricCipher(randomGenerator) {
    this.converters = new utils.Converters();
    this.bitOperators = new utils.BitOperators();
    this.randomGenerator = randomGenerator;
  }

  CryptoForgeSymmetricCipher.prototype = {

    /**
     * Encrypts some data.
     *
     * @function
     * @param data
     *            {String} Data to be encrypted, as string in Base64 encoded
     *            format.
     * @param keyB64
     *            {String} Key, as string in Base64 encoded format.
     * @returns Initialization vector concatenated with the encrypted data,
     *          as string in Base64 encoded format.
     */
    encrypt: function(keyB64, dataB64) {
      try {
        var key = this.converters.base64Decode(keyB64);
        var data = this.converters.base64Decode(dataB64);

        if (key.length !== policies.cipher.algorithm.keyLengthBytes) {
          throw new exceptions.CryptolibException(
              'Key length does not match the required by the algorithm.');
        }

        var initVec = this.randomGenerator.nextRandom(
            policies.cipher.initializationVectorLengthBytes);

        // Create a byte buffer for data.
        var dataBuffer = new box.forge.util.ByteBuffer(data);

        var cipher =
            box.forge.cipher.createCipher(policies.cipher.algorithm.name, key);

        // Only for the GCM mode
        var gcmAuthTagByteLength = policies.cipher.algorithm.tagLengthBytes;

        if (typeof gcmAuthTagBitLength !== 'undefined') {
          cipher.start({iv: initVec, tagLength: gcmAuthTagByteLength});
        } else {
          cipher.start({iv: initVec});
        }

        cipher.update(dataBuffer);
        cipher.finish();

        var encryptedData = cipher.output.getBytes().toString();

        if (typeof gcmAuthTagByteLength !== 'undefined') {
          var gcmAuthTag = cipher.mode.tag.getBytes();
          encryptedData = encryptedData + gcmAuthTag.toString();
        }

        var initVectorAndEncryptedData = initVec + encryptedData;
        var initVectorAndEncryptedDataBase64 =
            this.converters.base64Encode(initVectorAndEncryptedData);

        return initVectorAndEncryptedDataBase64;
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'Data could not be symmetrically encrypted.', error);
      }
    },

    /**
     * Decrypts some encrypted data, using the initialization vector that is
     * provided
     *
     * @function
     * @param keyB64
     *            {String} the key as a string in Base64 encoded format.
     * @param initVectorAndEncryptedDataBase64
     *            {String} Initialization vector concatenated with the
     *            encrypted data, as string in Base64 encoded format.
     * @returns Decrypted data, as string in Base64 encoded format.
     */
    decrypt: function(keyB64, initVectorAndEncryptedDataBase64) {
      try {
        var key = this.converters.base64Decode(keyB64);

        if (key.length !== policies.cipher.algorithm.keyLengthBytes) {
          throw new exceptions.CryptolibException(
              'Key length does not match the required by the algorithm.');
        }

        var cipher = box.forge.cipher.createDecipher(
            policies.cipher.algorithm.name, key);

        var initVectorAndEncryptedData =
            this.converters.base64Decode(initVectorAndEncryptedDataBase64);
        var initVector = this.bitOperators.extract(
            initVectorAndEncryptedData, 0,
            policies.cipher.initializationVectorLengthBytes);
        var encryptedData = this.bitOperators.extract(
            initVectorAndEncryptedData,
            policies.cipher.initializationVectorLengthBytes,
            initVectorAndEncryptedData.length);

        // Only for the GCM mode
        var gcmAuthTagByteLength = policies.cipher.algorithm.tagLengthBytes;

        if (typeof gcmAuthTagByteLength !== 'undefined') {
          var offset = encryptedData.length - gcmAuthTagByteLength;

          var gcmAuthTag = this.bitOperators.extract(
              encryptedData, offset, encryptedData.length);

          encryptedData = this.bitOperators.extract(encryptedData, 0, offset);

          var gcmAuthTagBitLength =
              gcmAuthTagByteLength * box.NUMBER_OF_BITS_PER_BYTE;

          cipher.start({
            iv: initVector,
            tagLength: gcmAuthTagBitLength,
            tag: gcmAuthTag
          });

        } else {
          cipher.start({iv: initVector});
        }

        var encryptedDataBuffer = new box.forge.util.ByteBuffer(encryptedData);

        cipher.update(encryptedDataBuffer);
        cipher.finish();

        var outputBase64 =
            this.converters.base64Encode(cipher.output.getBytes().toString());
        return outputBase64;

      } catch (error) {
        throw new exceptions.CryptoLibException(
            'Data could not be symmetrically decrypted.', error);
      }
    }
  };
};
/*
 * 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
 */
cryptolib.modules.symmetric = cryptolib.modules.symmetric || {};

/**
 * Defines the MACs of the module.
 *
 * @namespace symmetric/mac
 */
cryptolib.modules.symmetric.mac = function(box) {
  'use strict';

  box.symmetric = box.symmetric || {};
  box.symmetric.mac = {};

  /**
   * Allows a symmetric MAC factory to be obtained.
   *
   * @exports symmetric/mac/factory
   */
  box.symmetric.mac.factory = {};

  var policies = {
    mac: {
      hash: box.policies.symmetric.mac.hash,
      provider: box.policies.symmetric.mac.provider
    }
  };

  var converters;
  var exceptions;

  cryptolib('commons', function(box) {
    converters = new box.commons.utils.Converters();
    exceptions = box.commons.exceptions;
  });

  /**
   * Factory class that allows a MAC to be obtained.
   *
   * @class
   */
  box.symmetric.mac.factory.MacFactory = function() {};

  box.symmetric.mac.factory.MacFactory.prototype = {

    /**
     * Create a new MAC.
     * <p>
     * The particular MAC that is returned depends on the policies.
     *
     * @function
     * @returns a new MAC.
     */
    create: function() {
      try {
        if (policies.mac.provider === Config.symmetric.mac.provider.FORGE) {
          return this.createCryptoForgeMac();
        } else {
          throw new exceptions.CryptoLibException(
              'No suitable provider was provided.');
        }
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'A CryptoMac could not be obtained.', error);
      }
    },

    /**
     * Create a new CryptoForgeMac.
     *
     * @function
     * @returns {symmetric/mac.CryptoForgeMac} a new CryptoForgeMac.
     */
    createCryptoForgeMac: function() {
      return new CryptoForgeMac();
    }
  };

  /**
   * Represents a MAC.
   *
   * @class
   * @memberof symmetric/mac
   */
  function CryptoForgeMac() {
    this.hmacDigester = forge.hmac.create();
  }

  CryptoForgeMac.prototype = {

    /**
     * Generates MAC for some data.
     *
     * @function
     * @param secretKeyBase64
     *            {String} Secret key, as string in Base64 encoded format
     * @param arrayDataBase64
     *            {String} The input data for the MAC, as an array of string
     *            in Base64 encoded format. It is assumed that the array is
     *            not empty.
     * @returns The generated MAC, in Base64 encoded format.
     */
    generate: function(secretKeyBase64, arrayDataBase64) {

      var secretKey = converters.base64Decode(secretKeyBase64);
      var secretKeyBuffer = new forge.util.ByteBuffer(secretKey);

      try {
        this.hmacDigester.start(policies.mac.hash, secretKeyBuffer);
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'MAC message digester could not be initialized.', error);
      }

      try {
        if (arrayDataBase64.length < 1) {
          throw new exceptions.CryptoLibException(
              'The array of data should have at least one element');
        }

        var dataB64;
        for (var i = 0; i < arrayDataBase64.length; i++) {
          dataB64 = arrayDataBase64[i];
          var data = converters.base64Decode(dataB64);
          this.hmacDigester.update(data);
        }
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'MAC message digester could not be updated.', error);
      }

      try {
        var hmacMessageDigest = this.hmacDigester.digest();

        return converters.base64Encode(hmacMessageDigest.getBytes());
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'MAC message digest could not be generated.', error);
      }
    },

    /**
     * Verify that a given MAC is indeed the MAC for the given data, using
     * the given secret key.
     *
     * @function
     * @param macBase64
     *            {String} The MAC to be verified, as string in Base64
     *            encoded format.
     * @param secretKeyBase64
     *            {String} Secret key, as string in Base64 encoded format
     * @param arrayDataBase64
     *            {String} The input data for the MAC, as an array of string
     *            in Base64 encoded format. It is assumed that the array is
     *            not empty.
     *
     * @returns True if the MAC is the MAC of the given data and SecretKey,
     *          false otherwise.
     */
    verify: function(macBase64, secretKeyBase64, arrayDataBase64) {
      if (typeof macBase64 === 'undefined' || macBase64 === null) {
        return false;
      }

      var macBase64ToVerify = this.generate(secretKeyBase64, arrayDataBase64);

      if (macBase64.length !== macBase64ToVerify.length) {
        return false;
      }

      var equals = true;
      for (var i = 0; i < macBase64ToVerify.length; i++) {
        if (macBase64ToVerify.charCodeAt(i) !== macBase64.charCodeAt(i)) {
          equals = false;
        }
      }
      return equals;
    }
  };
};

/*
 * 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
 */
cryptolib.modules.symmetric = cryptolib.modules.symmetric || {};

/**
 * Defines a symmetric secret key
 *
 * @namespace symmetric/secretkey
 */
cryptolib.modules.symmetric.secretkey = function(box) {
  'use strict';

  box.symmetric = box.symmetric || {};
  box.symmetric.secretkey = {};

  /**
   * Allows a symmetric secret key factory to be obtained.
   *
   * @exports symmetric/secretkey/factory
   */
  box.symmetric.secretkey.factory = {};

  var policies = {
    secretkey: {
      encryption: {
        lengthBytes: box.policies.symmetric.secretkey.encryption.lengthBytes
      },
      mac: {lengthBytes: box.policies.symmetric.secretkey.mac.lengthBytes}
    }
  };

  var utils, randomFactory, exceptions, secureRandom, lengthBytes;

  var f = function(box) {
    utils = box.commons.utils;
    exceptions = box.commons.exceptions;
    randomFactory =
        new box.primitives.securerandom.factory.SecureRandomFactory();
  };

  f.policies = {
    primitives: {
      secureRandom:
          {provider: box.policies.symmetric.secretkey.secureRandom.provider}
    }
  };

  cryptolib('commons', 'primitives.securerandom', f);

  /**
   * Validates the received length argument, in bytes.
   *
   * Confirms that:
   * <ul>
   * <li>the type of <code>lengthBytes</code> is number.</li>
   * <li><code>lengthBytes</code> is an integer with a value not less than
   * 1.</li>
   * </ul>
   *
   * @function validateParameter
   * @param lengthBytes
   *            {number} the length, in bytes, to be validated.
   */
  var validateParameter = function(lengthBytes) {
    if (typeof lengthBytes !== 'number' || lengthBytes % 1 !== 0 ||
        lengthBytes < 1) {
      throw new exceptions.CryptoLibException(
          'The given key length is not valid.');
    }
  };

  /**
   * Represents a secret key factory.
   *
   * @class
   */
  box.symmetric.secretkey.factory.SecretKeyFactory = function() {
    secureRandom = randomFactory.getCryptoRandomBytes();
  };

  box.symmetric.secretkey.factory.SecretKeyFactory.prototype = {

    /**
     * Get a CryptoSecretKeyGenerator for encryption.
     *
     * @function
     * @returns {symmetric/secretkey.CryptoSecretKeyGenerator} a
     *          CryptoSecretKeyGenerator for encryption.
     */
    getCryptoSecretKeyGeneratorForEncryption: function() {
      lengthBytes = policies.secretkey.encryption.lengthBytes;
      return this.getCryptoSecretKeyGenerator();
    },

    /**
     * Get a CryptoSecretKeyGenerator for MAC.
     *
     * @function
     * @returns {symmetric/secretkey.CryptoSecretKeyGenerator} a
     *          CryptoSecretKeyGenerator for MAC.
     */
    getCryptoSecretKeyGeneratorForMac: function() {
      lengthBytes = policies.secretkey.mac.lengthBytes;
      return this.getCryptoSecretKeyGenerator();
    },

    /**
     * Create a new CryptoSecretKeyGenerator.
     *
     * @function
     * @returns {symmetric/secretkey.CryptoSecretKeyGenerator} a
     *          CryptoSecretKeyGenerator.
     */
    getCryptoSecretKeyGenerator: function() {
      return new CryptoSecretKeyGenerator(secureRandom);
    }
  };

  /**
   * Defines a Secret Key generator.
   *
   * @class
   * @memberof symmetric/secretkey
   * @param randomGenerator
   *            {CryptoScytlRandomBytes} a secure random bytes generator.
   */
  function CryptoSecretKeyGenerator(randomGenerator) {
    this.converters = new utils.Converters();
    this.randomGenerator = randomGenerator;
  }

  CryptoSecretKeyGenerator.prototype = {

    /**
     * Generates a secret key.
     *
     * @function
     * @return A secret key, as string in Base64 encoded format.
     */
    generate: function() {

      try {
        validateParameter(lengthBytes);

        var key = this.randomGenerator.nextRandom(lengthBytes);
        var keyBase64 = this.converters.base64Encode(key);

        return keyBase64;

      } catch (error) {
        throw new exceptions.CryptoLibException(
            'CryptoSecretKey could not be generated.', error);
      }
    }
  };
};

/*
 * 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
 */
cryptolib.modules.symmetric = cryptolib.modules.symmetric || {};

/**
 * Defines the symmetric service.
 * @namespace symmetric/service
 */
cryptolib.modules.symmetric.service = function(box) {
  'use strict';

  box.symmetric = box.symmetric || {};
  /**
   * A module that provides symmetric cryptographic services.
   * @exports symmetric/service
   */
  box.symmetric.service = {};

  var cipherFactory, secretKeyFactory, macFactory;

  cryptolib(
      'symmetric.cipher', 'symmetric.secretkey', 'symmetric.mac',
      'commons.exceptions', function(box) {
        cipherFactory =
            new box.symmetric.cipher.factory.SymmetricCipherFactory();
        secretKeyFactory =
            new box.symmetric.secretkey.factory.SecretKeyFactory();
        macFactory = new box.symmetric.mac.factory.MacFactory();
      });

  /**
   * @function getSecretKeyForEncryption
   * @returns {string} a secret key as a string encoded in Base64.
   */
  box.symmetric.service.getSecretKeyForEncryption = function() {
    var secretKeyGenerator =
        secretKeyFactory.getCryptoSecretKeyGeneratorForEncryption();
    return secretKeyGenerator.generate();
  };

  /**
   * @function getSecretKeyForMac
   * @returns {string} a MAC as a string encoded in Base64.
   */
  box.symmetric.service.getSecretKeyForMac = function() {
    var secretKeyGenerator =
        secretKeyFactory.getCryptoSecretKeyGeneratorForMac();
    return secretKeyGenerator.generate();
  };


  /**
   * @function encrypt
   * @returns {string} the initialization vector concatenated with the encrypted data,
   * as string in Base64 encoded format.
   */
  box.symmetric.service.encrypt = function(secretKeyBase64, dataBase64) {
    var cipher = cipherFactory.getCryptoSymmetricCipher();
    return cipher.encrypt(secretKeyBase64, dataBase64);
  };

  /**
   * @function decrypt
   * @returns {string} the decrypted data, as a String in Base64 encoded format.
   */
  box.symmetric.service.decrypt = function(
      secretKeyBase64, initVectorAndEncryptedDataBase64) {
    var cipher = cipherFactory.getCryptoSymmetricCipher();
    return cipher.decrypt(secretKeyBase64, initVectorAndEncryptedDataBase64);
  };

  /**
   * @function getMac
   * @returns {string} a string in Base64 encoded format.
   */
  box.symmetric.service.getMac = function(secretKeyBase64, arrayDataBase64) {
    var cryptoMac = macFactory.create();
    return cryptoMac.generate(secretKeyBase64, arrayDataBase64);
  };

  /**
   * @function verifyMac
   * @returns {boolean} true if the data has been verified.
   */
  box.symmetric.service.verifyMac = function(
      macBase64, secretKeyBase64, arrayDataBase64) {
    var cryptoMac = macFactory.create();
    return cryptoMac.verify(macBase64, secretKeyBase64, arrayDataBase64);
  };
};
if (typeof cryptoPRNG !== 'undefined') {
  cryptolib.cryptoPRNG = cryptoPRNG;
  cryptolib.Config = Config;
  if (typeof module !== 'undefined') {
    module.exports = cryptolib;
  };
};