/* * 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); } } }