/* * 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 ElGamalPrivateKey = require('./private-key'); var ElGamalEncryptedElements = require('./encrypted-elements'); var validator = require('./input-validator'); module.exports = ElGamalDecrypter; /** * @class ElGamalDecrypter * @classdesc The ElGamal decrypter API. To instantiate this object, use the * method {@link ElGamalCryptographyService.newDecrypter}. * @hideconstructor * @param {MathematicalService} * mathService The mathematical service to use. */ function ElGamalDecrypter(mathService) { var mathArrayCompressor_ = mathService.newArrayCompressor(); var privateKey_; /** * Initializes the ElGamal decrypter with the provided ElGamal private key. * * @function init * @memberof ElGamalDecrypter * @param {ElGamalPrivateKey} * privateKey The ElGamal private key with which to initialize * the ElGamal decrypter. * @returns {ElGamalDecrypter} A reference to this object, to facilitate * method chaining. * @throws {Error} * If the input data validation fails. */ this.init = function(privateKey) { checkElGamalPrivateKey( privateKey, 'ElGamal private key with which to initialize ElGamal decrypter'); privateKey_ = privateKey; return this; }; /** * ElGamal decrypts some ElGamal encrypted Zp group elements. Before using * this method, the decrypter must have been initialized with an ElGamal * private key, via the method {@link ElGamalDecrypter.init}. *

* The number of phi elements that comprise the encryption must be less than * or equal to the number of exponents in the ElGamal private key that was * used to initialize the ElGamal decrypter. * * @function decrypt * @memberof ElGamalDecrypter * @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 private key. * WARNING: This operation is computationally costly and * increases linearly with the number of encrypted elements. * @returns {ZpGroupElement[]} The decrypted Zp group elements. * @throws {Error} * If the input data validation fails. */ this.decrypt = function(encryptedElements, options) { if (typeof privateKey_ === 'undefined') { throw new Error( 'Could not ElGamal decrypt; Decrypter has not been initialized with any ElGamal private key'); } options = options || {}; checkDecryptionData(privateKey_, encryptedElements, options); var gamma = encryptedElements.gamma; var phis = encryptedElements.phis; var checkMembership = options.checkMembership || false; if (checkMembership) { var group = privateKey_.group; 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 private key.'); } } } var compressedPrivateKey = compressPrivateKey(privateKey_, phis.length); var privateKeyExponents = compressedPrivateKey.exponents; var decryptedElements = []; for (var j = 0; j < phis.length; j++) { decryptedElements.push(gamma.exponentiate(privateKeyExponents[j].negate()) .multiply(phis[j])); } return decryptedElements; }; function checkElGamalPrivateKey(privateKey, label) { validator.checkElGamalPrivateKey(privateKey, label); validator.checkIsInstanceOf( privateKey, ElGamalPrivateKey, 'ElGamalPrivateKey', label); } function checkElGamalEncryptedElements(elements, label) { validator.checkElGamalEncryptedElements(elements, label); validator.checkIsInstanceOf( elements, ElGamalEncryptedElements, 'ElGamalEncryptedElements', label); } function checkDecryptionData(privateKey, encryptedElements, options) { checkElGamalEncryptedElements( encryptedElements, 'ElGamal encrypted elements to decrypt'); var numPrivateKeyExponents = privateKey.exponents.length; var numPhis = encryptedElements.phis.length; if (numPrivateKeyExponents < numPhis) { throw new Error( 'Expected number of phi elements to decrypt to be less than or equal to number of private key exponents: ' + numPrivateKeyExponents + ' ; Found: ' + numPhis); } var confirmMembership = options.confirmMembership; if (typeof confirmMembership !== 'undefined') { validator.checkIsType( confirmMembership, 'boolean', '\"Confirm group membership\" flag for decryption'); } } function compressPrivateKey(privateKey, numPhis) { var group = privateKey.group; var exponents = privateKey.exponents; if (exponents.length === numPhis) { return privateKey; } else { var compressedExponents = mathArrayCompressor_.compressTrailingExponents(exponents, numPhis); return new ElGamalPrivateKey(group, compressedExponents); } } }