/* * 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, then * each of the encryption elements will be checked for membership * in the Zp subgroup associated with the public key. * WARNING: This operation is computationally costly and increases * linearly with the number of encrypted elements. * @param {boolean} * [options.useShortExponent=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); } } }