/* * 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}. *
* 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 true
, then
* a short
secret exponent will be generated for
* the pre-computation process.
* @param {boolean}
* [options.saveSecret=false] If true
, the secret
* exponent will be saved and returned, along with the encrypted
* elements. CAUTION: 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 true
, then
* a short
secret exponent will be generated for
* the pre-computation process.
* @param {boolean}
* [options.saveSecret=false] If true
, the secret
* exponent will be saved and returned, along with the encrypted
* elements. CAUTION: 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);
}
}
}