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