123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293 |
- /*
- * Copyright 2018 Scytl Secure Electronic Voting SA
- *
- * All rights reserved
- *
- * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
- */
- /* jshint node:true */
- 'use strict';
- var ElGamalPublicKey = require('./public-key');
- var ElGamalEncryptedElements = require('./encrypted-elements');
- var validator = require('./input-validator');
- var forge = require('node-forge');
- var BigInteger = forge.jsbn.BigInteger;
- module.exports = ElGamalEncrypter;
- /**
- * @class ElGamalEncrypter
- * @classdesc The ElGamal encrypter API. To instantiate this object, use the
- * method {@link ElGamalCryptographyService.newEncrypter}.
- * @hideconstructor
- * @param {MathematicalService}
- * mathService The mathematical service to use.
- */
- function ElGamalEncrypter(mathService) {
- var mathService_ = mathService;
- var mathArrayCompressor_ = mathService_.newArrayCompressor();
- var mathRandomGenerator_ = mathService_.newRandomGenerator();
- var publicKey_;
- var secureRandomGenerator_;
- /**
- * Initializes the ElGamal encrypter with the provided ElGamal public key.
- *
- * @function init
- * @memberof ElGamalEncrypter
- * @param {ElGamalPublicKey}
- * publicKey The ElGamal public key with which to initialize the
- * ElGamal encrypter.
- * @param {Object}
- * [options] An object containing optional arguments.
- * @param {SecureRandomGenerator}
- * [options.secureRandomGenerator=Created internally] The secure
- * random generator to use.
- * @returns {ElGamalEncrypter} A reference to this object, to facilitate
- * method chaining.
- * @throws {Error}
- * If the input data validation fails.
- */
- this.init = function(publicKey, options) {
- checkElGamalPublicKey(
- publicKey,
- 'ElGamal public key with which to initialize ElGamal encrypter');
- publicKey_ = publicKey;
- options = options || {};
- secureRandomGenerator_ = options.secureRandomGenerator;
- return this;
- };
- /**
- * ElGamal encrypts some Zp group elements. Before using this method, the
- * encrypter must have been initialized with an ElGamal public key, via the
- * method {@link ElGamalEncrypter.init}.
- * <p>
- * The number of elements to encrypt must be less than or equal to the
- * number of elements in the ElGamal public key that was used to initialize
- * the ElGamal encrypter.
- *
- * @function encrypt
- * @memberof ElGamalEncrypter
- * @param {ZpGroupElement[]|string[]}
- * elements The Zp group elements to encrypt. The Zp group
- * elements may also be provided in the form of BigInteger string
- * values.
- * @param {Object}
- * [options] An object containing optional arguments.
- * @param {ElGamalEncryptedElements}
- * [options.preComputation=Generated internally] A
- * pre-computation of the ElGamal encryption.
- * @param {boolean}
- * [options.useShortExponent=false] If <code>true</code>, then
- * a <code>short</code> secret exponent will be generated for
- * the pre-computation process.
- * @param {boolean}
- * [options.saveSecret=false] If <code>true</code>, the secret
- * exponent will be saved and returned, along with the encrypted
- * elements. <b>CAUTION:</b> Use only when the secret is really
- * needed later (e.g. for zero-knowledge proof generation).
- * @returns {ElGamalEncryptedElements} The encrypted Zp group elements.
- * @throws {Error}
- * If the input data validation fails, the encrypter was not
- * initialized or an attempt is made to ElGamal encrypt a
- * non-quadratic residue Zp subgroup, using a short exponent.
- */
- this.encrypt = function(elements, options) {
- if (typeof publicKey_ === 'undefined') {
- throw new Error(
- 'Could not ElGamal encrypt; Encrypter has not been initialized with any ElGamal public key');
- }
- options = options || {};
- checkEncryptionData(publicKey_, elements, options);
- if (typeof elements[0] === 'string') {
- elements = getGroupElementsFromStrings(publicKey_.group, elements);
- }
- var preComputation;
- if (typeof options.preComputation !== 'undefined') {
- preComputation = options.preComputation;
- } else {
- preComputation = this.preCompute({
- useShortExponent: options.useShortExponent,
- saveSecret: options.saveSecret
- });
- }
- var compressedPreComputation =
- compressPreComputation(preComputation, elements.length);
- var gamma = compressedPreComputation.gamma;
- var preComputedPhis = compressedPreComputation.phis;
- var phis = [];
- for (var i = 0; i < preComputedPhis.length; i++) {
- phis.push(elements[i].multiply(preComputedPhis[i]));
- }
- var saveSecret = options.saveSecret || false;
- if (!saveSecret) {
- return new ElGamalEncryptedElements(gamma, phis);
- } else {
- return new ElGamalEncryptedElements(gamma, phis, preComputation.secret);
- }
- };
- /**
- * Computes the part of the ElGamal encryption that can be performed before
- * knowing the Zp group elements that are to be encrypted.
- *
- * @function preCompute
- * @memberof ElGamalEncrypter
- * @param {Object}
- * [options] An object containing optional arguments.
- * @param {boolean}
- * [options.useShortExponent=false] If <code>true</code>, then
- * a <code>short</code> secret exponent will be generated for
- * the pre-computation process.
- * @param {boolean}
- * [options.saveSecret=false] If <code>true</code>, the secret
- * exponent will be saved and returned, along with the encrypted
- * elements. <b>CAUTION:</b> Use only when the secret is really
- * needed later (e.g. for zero-knowledge proof generation).
- * @returns {ElGamalEncryptedElements} The encryption pre-computation.
- * @throws {Error}
- * If the input data validation fails or an attempt is made to
- * ElGamal pre-compute a non-quadratic residue Zp subgroup,
- * using a short exponent.
- */
- this.preCompute = function(options) {
- if (typeof publicKey_ === 'undefined') {
- throw new Error(
- 'Could not ElGamal pre-compute; Encrypter has not been initialized with any ElGamal public key');
- }
- options = options || {};
- checkPreComputationData(publicKey_, options);
- var group = publicKey_.group;
- var publicKeyElements = publicKey_.elements;
- var useShortExponent = options.useShortExponent || false;
- var secret = mathRandomGenerator_.nextExponent(group, {
- secureRandomGenerator: secureRandomGenerator_,
- useShortExponent: useShortExponent
- });
- var gamma = group.generator.exponentiate(secret);
- var phis = [];
- for (var i = 0; i < publicKeyElements.length; i++) {
- phis.push(publicKeyElements[i].exponentiate(secret));
- }
- var saveSecret = options.saveSecret || false;
- if (!saveSecret) {
- return new ElGamalEncryptedElements(gamma, phis);
- } else {
- return new ElGamalEncryptedElements(gamma, phis, secret);
- }
- };
- function checkElGamalPublicKey(publicKey, label) {
- validator.checkElGamalPublicKey(publicKey, label);
- validator.checkIsInstanceOf(
- publicKey, ElGamalPublicKey, 'ElGamalPublicKey', label);
- }
- function checkElGamalEncryptedElements(elements, label) {
- validator.checkElGamalEncryptedElements(elements, label);
- validator.checkIsInstanceOf(
- elements, ElGamalEncryptedElements, 'ElGamalEncryptedElements', label);
- }
- function checkEncryptionData(publicKey, elements, options) {
- validator.checkIsArray(elements, 'Zp group elements to encrypt');
- if (typeof elements[0] !== 'string') {
- validator.checkZpGroupElements(
- elements, 'Zp group elements to encrypt', publicKey.group);
- } else {
- validator.checkIsStringArray(
- elements, 'Zp group element value strings to encrypt');
- }
- var numElements = elements.length;
- var numPublicKeyElements = publicKey.elements.length;
- if (numElements > numPublicKeyElements) {
- throw new Error(
- 'Expected number of Zp group elements to encrypt to be less than or equal to number of public key elements: ' +
- numPublicKeyElements + ' ; Found: ' + numElements);
- }
- var preComputation = options.preComputation;
- if (typeof preComputation !== 'undefined') {
- checkElGamalEncryptedElements(
- preComputation, 'ElGamal encryption pre-computation');
- var numPreComputedPhis = preComputation.phis.length;
- if (numElements > numPreComputedPhis) {
- throw new Error(
- 'Expected number of elements to encrypt to be less than or equal to number of pre-computed phi elements: ' +
- numPreComputedPhis + ' ; Found: ' + numElements);
- }
- }
- }
- function checkPreComputationData(publicKey, options) {
- var useShortExponent = options.useShortExponent;
- if (typeof useShortExponent !== 'undefined') {
- validator.checkIsType(
- useShortExponent, 'boolean',
- '\"Use short exponent\" flag for pre-computation');
- if (useShortExponent && !publicKey.group.isQuadraticResidueGroup()) {
- throw new Error(
- 'Attempt to ElGamal pre-compute using short exponent for Zp subgroup that is not of type quadratic residue.');
- }
- }
- var saveSecret = options.saveSecret;
- if (typeof saveSecret !== 'undefined') {
- validator.checkIsType(
- saveSecret, 'boolean', '\"Save secret\" flag for pre-computation');
- }
- }
- function getGroupElementsFromStrings(group, elementValueStrings) {
- var p = group.p;
- var q = group.q;
- var groupElements = [];
- var groupElement;
- for (var i = 0; i < elementValueStrings.length; i++) {
- groupElement = mathService_.newZpGroupElement(
- p, q, new BigInteger(elementValueStrings[i]));
- groupElements.push(groupElement);
- }
- return groupElements;
- }
- function compressPreComputation(preComputation, numElements) {
- var preComputedPhis = preComputation.phis;
- if (preComputedPhis.length === numElements) {
- return preComputation;
- } else {
- var compressedPreComputedPhis =
- mathArrayCompressor_.compressTrailingZpGroupElements(
- preComputedPhis, numElements);
- return new ElGamalEncryptedElements(
- preComputation.gamma, compressedPreComputedPhis);
- }
- }
- }
|