/* * 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 CommonTestData = require('./data/common-data'); var elGamal = require('../lib/index'); var mathematical = require('scytl-mathematical'); var secureRandom = require('scytl-securerandom'); describe('The ElGamal cryptography module should be able to ...', function() { var elGamalService_; var secureRandomService_; var mathService_; var mathRandomGenerator_; var group_; var publicKey_; var privateKey_; var publicKeyElements_; var privateKeyExponents_; var keyPair_; var elements_; var elementValueStrings_; var largePublicKey_; var largePrivateKey_; var elementsFromLargeGroup_; var elementsFromLargeNonQrGroup_; var identityElements_; var shortExponentBitLength_; var singleElements_; var elGamalEncrypter_; var elGamalDecrypter_; var encryptedElements_; var elGamalEncrypterLargeGroup_; var elGamalDecrypterLargeGroup_; var prngSeed_; var encrypterRandomGenerator_; var decrypterRandomGenerator_; beforeAll(function() { elGamalService_ = elGamal.newService(); secureRandomService_ = secureRandom.newService(); mathService_ = mathematical.newService({secureRandomService: secureRandomService_}); mathRandomGenerator_ = mathService_.newRandomGenerator(); var testData = new CommonTestData(); group_ = testData.getGroup(); publicKey_ = testData.getPublicKey(); privateKey_ = testData.getPrivateKey(); publicKeyElements_ = testData.getPublicKeyElements(); privateKeyExponents_ = testData.getPrivateKeyExponents(); keyPair_ = testData.getKeyPair(); elements_ = testData.getZpGroupElements(); elementValueStrings_ = testData.getZpGroupElementValueStrings(); largePublicKey_ = testData.getLargePublicKey(); largePrivateKey_ = testData.getLargePrivateKey(); elementsFromLargeGroup_ = testData.getElementsFromLargeZpSubgroup(); elementsFromLargeNonQrGroup_ = testData.getElementsFromLargeNonQrZpSubgroup(); identityElements_ = testData.getIdentityElements(); singleElements_ = testData.getSingleElements(); shortExponentBitLength_ = testData.getShortExponentBitLength(); elGamalEncrypter_ = elGamalService_.newEncrypter().init(publicKey_); elGamalDecrypter_ = elGamalService_.newDecrypter().init(privateKey_); encryptedElements_ = elGamalEncrypter_.encrypt(elements_); elGamalEncrypterLargeGroup_ = elGamalService_.newEncrypter().init(largePublicKey_); elGamalDecrypterLargeGroup_ = elGamalService_.newDecrypter().init(largePrivateKey_); prngSeed_ = secureRandomService_.nextSeed(); }); beforeEach(function() { encrypterRandomGenerator_ = secureRandom.newService({prngSeed: prngSeed_}).newRandomGenerator(); decrypterRandomGenerator_ = secureRandom.newService({prngSeed: prngSeed_}).newRandomGenerator(); }); describe('create an ElGamal cryptography service that should be able to ..', function() { it('create a new ElGamal key pair', function() { validatePublicKey(keyPair_.publicKey, group_); validatePrivateKey(keyPair_.privateKey, group_); }); it('create a new ElGamalEncryptedElements object', function() { var encryptedElements = elGamalEncrypter_.encrypt(elements_); var gamma = encryptedElements.gamma; var phis = encryptedElements.phis; var newEncryptedElements = elGamalService_.newEncryptedElements(gamma, phis); var decryptedElements = elGamalDecrypter_.decrypt(newEncryptedElements); checkDecryptedElements(decryptedElements, elements_); }); it('create a new ElGamalEncryptedElements object, with a saved secret', function() { var encryptedElementsAndSecret = elGamalEncrypter_.encrypt(elements_, {saveSecret: true}); var gamma = encryptedElementsAndSecret.gamma; var phis = encryptedElementsAndSecret.phis; var secret = encryptedElementsAndSecret.secret; var newEncryptedElementsAndSecret = elGamalService_.newEncryptedElements(gamma, phis, secret); expect((newEncryptedElementsAndSecret.secret.equals(secret))) .toBeTruthy(); var encryptedElements = elGamalService_.newEncryptedElements( newEncryptedElementsAndSecret.gamma, newEncryptedElementsAndSecret.phis); var decryptedElements = elGamalDecrypter_.decrypt(encryptedElements); checkDecryptedElements(decryptedElements, elements_); }); it('serialize and deserialize an ElGamal public key', function() { var publicKeyJson = publicKey_.toJson(); var publicKeyFromJson = elGamalService_.newPublicKey(publicKeyJson); validatePublicKey(publicKeyFromJson, group_); }); it('serialize and deserialize an ElGamal private key', function() { var privateKeyJson = privateKey_.toJson(); var privateKeyFromJson = elGamalService_.newPrivateKey(privateKeyJson); validatePrivateKey(privateKeyFromJson, group_); }); it('serialize and deserialize ElGamal encrypted elements', function() { var encryptedElements = elGamalEncrypter_.encrypt(elements_); var encryptedElementsJson = encryptedElements.toJson(); var encryptedElementsFromJson = elGamalService_.newEncryptedElements(encryptedElementsJson); var decryptedElements = elGamalDecrypter_.decrypt(encryptedElementsFromJson); checkDecryptedElements(decryptedElements, elements_); }); it('serialize and deserialize an encryption pre-computation', function() { var preComputation = elGamalEncrypter_.preCompute(); var preComputationJson = preComputation.toJson(); var preComputationFromJson = elGamalService_.newEncryptedElements(preComputationJson); var encryptedElements = elGamalEncrypter_.encrypt( elements_, {preComputation: preComputationFromJson}); var decryptedElements = elGamalDecrypter_.decrypt(encryptedElements); checkDecryptedElements(decryptedElements, elements_); }); describe('create an encrypter/decrypter pair that should be able to ..', function() { it('encrypt and decrypt some group elements', function() { var decryptedElements = elGamalDecrypter_.decrypt(encryptedElements_); checkDecryptedElements(decryptedElements, elements_); }); it('encrypt and decrypt some group elements, using a specified secure random service object', function() { var elGamalService = elGamal.newService({secureRandomService: secureRandomService_}); var elGamalEncrypter = elGamalService.newEncrypter().init(publicKey_); var elGamalDecrypter = elGamalService.newDecrypter().init(privateKey_); var encryptedElements = elGamalEncrypter.encrypt(elements_); var decryptedElements = elGamalDecrypter.decrypt(encryptedElements); checkDecryptedElements(decryptedElements, elements_); }); it('encrypt and decrypt some group elements, using a specified mathematical service object', function() { var elGamalService = elGamal.newService({mathematicalService: mathService_}); var elGamalEncrypter = elGamalService.newEncrypter().init(publicKey_); var elGamalDecrypter = elGamalService.newDecrypter().init(privateKey_); var encryptedElements = elGamalEncrypter.encrypt(elements_); var decryptedElements = elGamalDecrypter.decrypt(encryptedElements); checkDecryptedElements(decryptedElements, elements_); }); it('encrypt and decrypt some group element value strings', function() { var encryptedElements = elGamalEncrypter_.encrypt(elementValueStrings_); var decryptedElements = elGamalDecrypter_.decrypt(encryptedElements); checkDecryptedElements(decryptedElements, elements_); }); it('decrypt some group elements, with the check group membership flag set to true', function() { var decryptedElements = elGamalDecrypter_.decrypt( encryptedElements_, {checkMembership: true}); checkDecryptedElements(decryptedElements, elements_); }); it('decrypt some group elements, with check group membership flag set to false', function() { var decryptedElements = elGamalDecrypter_.decrypt( encryptedElements_, {checkMembership: false}); checkDecryptedElements(decryptedElements, elements_); }); it('throw an error when decrypting some group elements, the check group membership flag is set to true and the check fails', function() { expect(function() { elGamalDecrypterLargeGroup_.decrypt( encryptedElements_, {checkMembership: true}); }).toThrow(); }); it('encrypt and decrypt some group elements, using an encryption pre-computation', function() { var preComputation = elGamalEncrypter_.preCompute(); var encryptedElements = elGamalEncrypter_.encrypt( elements_, {preComputation: preComputation}); var decryptedElements = elGamalDecrypter_.decrypt(encryptedElements); checkDecryptedElements(decryptedElements, elements_); }); it('encrypt and decrypt some group elements and retrieve the secret exponent', function() { var encryptedElementsAndSecret = elGamalEncrypter_.encrypt(elements_, {saveSecret: true}); checkSecretExponent( publicKey_, encryptedElementsAndSecret, elements_); var encryptedElements = elGamalService_.newEncryptedElements( encryptedElementsAndSecret.gamma, encryptedElementsAndSecret.phis); var decryptedElements = elGamalDecrypter_.decrypt(encryptedElements); checkDecryptedElements(decryptedElements, elements_); }); it('encrypt and decrypt some group elements, using an encryption pre-computation, and retrieve the secret exponent after pre-computing', function() { var preComputationAndSecret = elGamalEncrypter_.preCompute({saveSecret: true}); checkSecretExponent( publicKey_, preComputationAndSecret, identityElements_); var preComputation = elGamalService_.newEncryptedElements( preComputationAndSecret.gamma, preComputationAndSecret.phis); var encryptedElements = elGamalEncrypter_.encrypt( elements_, {preComputation: preComputation}); var decryptedElements = elGamalDecrypter_.decrypt(encryptedElements); checkDecryptedElements(decryptedElements, elements_); }); it('encrypt and decrypt some group elements, using encrypted elements generated with a short exponent', function() { var encryptedElementsAndSecret = elGamalEncrypterLargeGroup_.encrypt( elementsFromLargeGroup_, {useShortExponent: true, saveSecret: true}); expect(encryptedElementsAndSecret.secret.value.bitLength()) .not.toBeGreaterThan(shortExponentBitLength_); var encryptedElements = elGamalService_.newEncryptedElements( encryptedElementsAndSecret.gamma, encryptedElementsAndSecret.phis); var decryptedElements = elGamalDecrypterLargeGroup_.decrypt(encryptedElements); checkDecryptedElements(decryptedElements, elementsFromLargeGroup_); }); it('encrypt and decrypt some group elements, using an encryption pre-computation generated with a short exponent', function() { var preComputationAndSecret = elGamalEncrypterLargeGroup_.preCompute( {useShortExponent: true, saveSecret: true}); expect(preComputationAndSecret.secret.value.bitLength()) .not.toBeGreaterThan(shortExponentBitLength_); var preComputation = elGamalService_.newEncryptedElements( preComputationAndSecret.gamma, preComputationAndSecret.phis); var encryptedElements = elGamalEncrypterLargeGroup_.encrypt( elementsFromLargeGroup_, {preComputation: preComputation}); var decryptedElements = elGamalDecrypterLargeGroup_.decrypt(encryptedElements); checkDecryptedElements(decryptedElements, elementsFromLargeGroup_); }); it('encrypt and decrypt some group elements, when the public key and private key undergo compression', function() { var encryptedElements = elGamalEncrypter_.encrypt([elements_[0]]); var decryptedElements = elGamalDecrypter_.decrypt(encryptedElements); checkDecryptedElements(decryptedElements, [elements_[0]]); }); it('throw an error when encrypting before the encrypter has been initialized with a public key', function() { var encrypter = elGamalService_.newEncrypter(); expect(function() { encrypter.encrypt(elements_); }).toThrow(); }); it('throw an error when decrypting before the decrypter has been initialized with a private key', function() { var decrypter = elGamalService_.newDecrypter(); expect(function() { decrypter.decrypt(encryptedElements_); }).toThrow(); }); it('throw an error when decrypting before the PRNG decrypter has been initialized with a public key', function() { var prngDecrypter = elGamalService_.newPrngDecrypter(); expect(function() { prngDecrypter.decrypt(encryptedElements_); }).toThrow(); }); it('throw an error when attempting to decrypt non-group members and group membership is being checked', function() { expect(function() { var encryptedElements = elGamalEncrypterLargeGroup_.encrypt(elementsFromLargeGroup_); elGamalDecrypter_.decrypt( encryptedElements, {checkMembership: true}); }).toThrow(); }); it('throw an error when attempting to ElGamal encrypt, using a short exponent, for a group that is not of type quadratic residue', function() { expect(function() { elGamalEncrypterLargeGroup_.encrypt( elementsFromLargeNonQrGroup_, {useShortExponent: true}); }).toThrow(); }); it('throw an error when attempting to ElGamal encrypt, using a short exponent, for a group whose order is smaller than this exponent', function() { expect(function() { elGamalEncrypter_.encrypt(elements_, {useShortExponent: true}); }).toThrow(); }); }); describe( 'create an encrypter/PRNG decrypter pair that should be able to ..', function() { it('encrypt and decrypt some group elements', function() { var encrypterFromSeed = elGamalService_.newEncrypter().init( publicKey_, {secureRandomGenerator: encrypterRandomGenerator_}); var encryptedElements = encrypterFromSeed.encrypt(elements_); var prngDecrypter = elGamalService_.newPrngDecrypter().init( publicKey_, decrypterRandomGenerator_); var decryptedElements = prngDecrypter.decrypt(encryptedElements); checkDecryptedElements(decryptedElements, elements_); }); it('encrypt and decrypt some group elements, using an encryption pre-computation', function() { var encrypterFromSeed = elGamalService_.newEncrypter().init( publicKey_, {secureRandomGenerator: encrypterRandomGenerator_}); var preComputation = encrypterFromSeed.preCompute(); var encryptedElements = encrypterFromSeed.encrypt( elements_, {preComputation: preComputation}); var prngDecrypter = elGamalService_.newPrngDecrypter().init( publicKey_, decrypterRandomGenerator_); var decryptedElements = prngDecrypter.decrypt(encryptedElements); checkDecryptedElements(decryptedElements, elements_); }); it('encrypt some group elements and retrieve the secret exponent that will be used for decryption', function() { var encrypterFromSeed = elGamalService_.newEncrypter().init( publicKey_, {secureRandomGenerator: encrypterRandomGenerator_}); var encryptedElementsAndSecret = encrypterFromSeed.encrypt(elements_, {saveSecret: true}); var secret = encryptedElementsAndSecret.secret; var secretFromDecrypter = mathRandomGenerator_.nextExponent( group_, {secureRandomGenerator: decrypterRandomGenerator_}); expect(secretFromDecrypter.equals(secret)).toBeTruthy(); }); it('encrypt some group elements, using pre-computation, and retrieve the secret exponent that will be used for decryption', function() { var encrypterFromSeed = elGamalService_.newEncrypter().init( publicKey_, {secureRandomGenerator: encrypterRandomGenerator_}); var preComputationAndSecret = encrypterFromSeed.preCompute({saveSecret: true}); var secret = preComputationAndSecret.secret; var secretFromDecrypter = mathRandomGenerator_.nextExponent( group_, {secureRandomGenerator: decrypterRandomGenerator_}); expect(secretFromDecrypter.equals(secret)).toBeTruthy(); }); it('encrypt and decrypt some group elements, using encrypted elements generated with a short exponent', function() { var encrypterFromSeed = elGamalService_.newEncrypter().init( largePublicKey_, {secureRandomGenerator: encrypterRandomGenerator_}); var encryptedElementsAndSecret = encrypterFromSeed.encrypt( elementsFromLargeGroup_, {useShortExponent: true, saveSecret: true}); expect(encryptedElementsAndSecret.secret.value.bitLength()) .not.toBeGreaterThan(shortExponentBitLength_); var encryptedElements = elGamalService_.newEncryptedElements( encryptedElementsAndSecret.gamma, encryptedElementsAndSecret.phis); var prngDecrypter = elGamalService_.newPrngDecrypter().init( largePublicKey_, decrypterRandomGenerator_); var decryptedElements = prngDecrypter.decrypt( encryptedElements, {useShortExponent: true}); checkDecryptedElements( decryptedElements, elementsFromLargeGroup_); }); it('encrypt and decrypt some group elements, using an encryption pre-computation generated with a short exponent', function() { var encrypterFromSeed = elGamalService_.newEncrypter().init( largePublicKey_, {secureRandomGenerator: encrypterRandomGenerator_}); var preComputationAndSecret = encrypterFromSeed.preCompute( {useShortExponent: true, saveSecret: true}); expect(preComputationAndSecret.secret.value.bitLength()) .not.toBeGreaterThan(shortExponentBitLength_); var preComputation = elGamalService_.newEncryptedElements( preComputationAndSecret.gamma, preComputationAndSecret.phis); var encryptedElements = encrypterFromSeed.encrypt( elementsFromLargeGroup_, {preComputation: preComputation}); var prngDecrypter = elGamalService_.newPrngDecrypter().init( largePublicKey_, decrypterRandomGenerator_); var decryptedElements = prngDecrypter.decrypt( encryptedElements, {useShortExponent: true}); checkDecryptedElements( decryptedElements, elementsFromLargeGroup_); }); it('encrypt and decrypt an array of single group elements', function() { var encrypterFromSeed = elGamalService_.newEncrypter().init( largePublicKey_, {secureRandomGenerator: encrypterRandomGenerator_}); var encryptedElementArray = []; for (var i = 0; i < singleElements_.length; i++) { encryptedElementArray.push( encrypterFromSeed.encrypt([singleElements_[i]])); } var prngDecrypter = elGamalService_.newPrngDecrypter().init( largePublicKey_, decrypterRandomGenerator_); var decryptedElementArray = []; for (var j = 0; j < encryptedElementArray.length; j++) { decryptedElementArray.push( prngDecrypter.decrypt(encryptedElementArray[j])[0]); } checkDecryptedElements(decryptedElementArray, singleElements_); }); }); }); function validatePublicKey(publicKey, group) { var elements = publicKey.elements; var numElements = elements.length; expect(numElements).toBe(publicKeyElements_.length); for (var i = 0; i < numElements; i++) { expect(elements[i].value.toString()) .toEqual(publicKeyElements_[i].value.toString()); } expect(publicKey.group.equals(group)).toBeTruthy(); } function validatePrivateKey(privateKey, group) { var exponents = privateKey.exponents; var numExponents = exponents.length; expect(numExponents).toBe(privateKeyExponents_.length); for (var i = 0; i < numExponents; i++) { expect(exponents[i].value.toString()) .toEqual(privateKeyExponents_[i].value.toString()); } expect(privateKey.group.equals(group)).toBeTruthy(); } function checkDecryptedElements(decryptedElements, elements) { expect(decryptedElements.length).toBe(elements.length); for (var i = 0; i < decryptedElements.length; i++) { expect(decryptedElements[i].equals(elements[i])).toBeTruthy(); } } function checkSecretExponent( publicKey, encryptedElementsAndSecret, elements) { var publicKeyElements = publicKey.elements; var gamma = encryptedElementsAndSecret.gamma; var phis = encryptedElementsAndSecret.phis; var secret = encryptedElementsAndSecret.secret; expect((group_.generator.exponentiate(secret)).equals(gamma)).toBeTruthy(); for (var i = 0; i < phis.length; i++) { expect((publicKeyElements[i].exponentiate(secret).multiply(elements[i])) .equals(phis[i])) .toBeTruthy(); } } });