/* * 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'); module.exports = ElGamalPrngDecrypter; /** * @class ElGamalPrngDecrypter * @classdesc The ElGamal PRNG decrypter API. To instantiate this object, use * the method {@link ElGamalCryptographyService.newPrngDecrypter}. * @hideconstructor * @param {MathematicalService} * mathService The mathematical service to use. */ function ElGamalPrngDecrypter(mathService) { var mathArrayCompressor_ = mathService.newArrayCompressor(); var mathRandomGenerator_ = mathService.newRandomGenerator(); var publicKey_; var secureRandomGenerator_; /** * Initializes the ElGamal PRNG decrypter with the provided ElGamal public * key and secure random generator. * * @function init * @memberof ElGamalPrngDecrypter * @param {ElGamalPublicKey} * publicKey The ElGamal public key with which to initialize the * ElGamal PRNG decrypter. * @param {SecureRandomGenerator} * secureRandomGenerator The secure random generator which which * to initialize the ElGamal PRNG decrypter. * @returns {ElGamalPrngDecrypter} A reference to this object, to facilitate * method chaining. * @throws {Error} * If the input data validation fails. */ this.init = function(publicKey, secureRandomGenerator) { checkElGamalPublicKey( publicKey, 'ElGamal public key with which to initialize ElGamal PRNG decrypter'); validator.checkIsObjectWithProperties( secureRandomGenerator, 'Secure random generator with which to initialize ElGamal PRNG decrypter'); publicKey_ = publicKey; secureRandomGenerator_ = secureRandomGenerator; return this; }; /** * ElGamal decrypts some ElGamal encrypted Zp group elements. Before using * this method, the decrypter must have been initialized with an ElGamal * public key and a secure random generator, via the method * {@link ElGamalPrngDecrypter.init}. *
* The number of phi elements that comprise the encryption must be less than
* or equal to the number of elements in the ElGamal public key that was
* used to initialize the ElGamal PRNG decrypter.
*
* @function decrypt
* @memberof ElGamalPrngDecrypter
* @param {ElGamalEncryptedElements}
* encryptedElements The ElGamal encrypted Zp group elements.
* @param {Object}
* [options] An object containing optional arguments.
* @param {boolean}
* [options.confirmMembership=false] If true
, a
* short
secret exponent was used for the
* encryption and must be regenerated for the decryption.
* @returns {ZpGroupElement[]} The decrypted Zp group elements.
* @throws {Error}
* If the input data validation fails.
*/
this.decrypt = function(encryptedElements, options) {
if (typeof publicKey_ === 'undefined') {
throw new Error(
'Could not ElGamal decrypt; PRNG decrypter has not been initialized with any ElGamal public key');
}
options = options || {};
checkDecryptionData(publicKey_, encryptedElements, options);
var phis = encryptedElements.phis;
var group = publicKey_.group;
var checkMembership = options.checkMembership || false;
if (checkMembership) {
for (var i = 0; i < phis.length; i++) {
if (!(group.isGroupMember(phis[i]))) {
throw new Error(
'Found phi element with value: ' + phis[i].value +
' that is not member of Zp subgroup associated with public key.');
}
}
}
var compressedPublicKey = compressPublicKey(publicKey_, phis.length);
var publicKeyElements = compressedPublicKey.elements;
var useShortExponent = options.useShortExponent || false;
var secret = mathRandomGenerator_.nextExponent(group, {
secureRandomGenerator: secureRandomGenerator_,
useShortExponent: useShortExponent
});
var negatedSecret = secret.negate();
var decryptedElements = [];
for (var j = 0; j < encryptedElements.phis.length; j++) {
decryptedElements.push(
publicKeyElements[j].exponentiate(negatedSecret).multiply(phis[j]));
}
return decryptedElements;
};
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 checkDecryptionData(publicKey, encryptedElements, options) {
checkElGamalEncryptedElements(
encryptedElements, 'ElGamal encrypted elements to decrypt, using PRNG');
var numPublicKeyElements = publicKey.elements.length;
var numPhis = encryptedElements.phis.length;
if (numPublicKeyElements < numPhis) {
throw new Error(
'Expected number of phi elements to decrypt, using PRNG, to be less than or equal to number of public key elements: ' +
numPublicKeyElements + ' ; Found: ' + numPhis);
}
var confirmMembership = options.confirmMembership;
if (typeof confirmMembership !== 'undefined') {
validator.checkIsType(
confirmMembership, 'boolean',
'\"Confirm group membership\" flag for decryption, using PRNG');
}
var useShortExponent = options.useShortExponent;
if (typeof useShortExponent !== 'undefined') {
validator.checkIsType(
useShortExponent, 'boolean',
'\"Use shoft exponent\" flag for decryption, using PRNG');
}
}
function compressPublicKey(publicKey, numPhis) {
var elements = publicKey.elements;
var group = publicKey.group;
if (elements.length === numPhis) {
return publicKey;
} else {
var compressedElements =
mathArrayCompressor_.compressTrailingZpGroupElements(
elements, numPhis);
return new ElGamalPublicKey(group, compressedElements);
}
}
}