encrypter.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. /*
  2. * Copyright 2018 Scytl Secure Electronic Voting SA
  3. *
  4. * All rights reserved
  5. *
  6. * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
  7. */
  8. /* jshint node:true */
  9. 'use strict';
  10. var ElGamalPublicKey = require('./public-key');
  11. var ElGamalEncryptedElements = require('./encrypted-elements');
  12. var validator = require('./input-validator');
  13. var forge = require('node-forge');
  14. var BigInteger = forge.jsbn.BigInteger;
  15. module.exports = ElGamalEncrypter;
  16. /**
  17. * @class ElGamalEncrypter
  18. * @classdesc The ElGamal encrypter API. To instantiate this object, use the
  19. * method {@link ElGamalCryptographyService.newEncrypter}.
  20. * @hideconstructor
  21. * @param {MathematicalService}
  22. * mathService The mathematical service to use.
  23. */
  24. function ElGamalEncrypter(mathService) {
  25. var mathService_ = mathService;
  26. var mathArrayCompressor_ = mathService_.newArrayCompressor();
  27. var mathRandomGenerator_ = mathService_.newRandomGenerator();
  28. var publicKey_;
  29. var secureRandomGenerator_;
  30. /**
  31. * Initializes the ElGamal encrypter with the provided ElGamal public key.
  32. *
  33. * @function init
  34. * @memberof ElGamalEncrypter
  35. * @param {ElGamalPublicKey}
  36. * publicKey The ElGamal public key with which to initialize the
  37. * ElGamal encrypter.
  38. * @param {Object}
  39. * [options] An object containing optional arguments.
  40. * @param {SecureRandomGenerator}
  41. * [options.secureRandomGenerator=Created internally] The secure
  42. * random generator to use.
  43. * @returns {ElGamalEncrypter} A reference to this object, to facilitate
  44. * method chaining.
  45. * @throws {Error}
  46. * If the input data validation fails.
  47. */
  48. this.init = function(publicKey, options) {
  49. checkElGamalPublicKey(
  50. publicKey,
  51. 'ElGamal public key with which to initialize ElGamal encrypter');
  52. publicKey_ = publicKey;
  53. options = options || {};
  54. secureRandomGenerator_ = options.secureRandomGenerator;
  55. return this;
  56. };
  57. /**
  58. * ElGamal encrypts some Zp group elements. Before using this method, the
  59. * encrypter must have been initialized with an ElGamal public key, via the
  60. * method {@link ElGamalEncrypter.init}.
  61. * <p>
  62. * The number of elements to encrypt must be less than or equal to the
  63. * number of elements in the ElGamal public key that was used to initialize
  64. * the ElGamal encrypter.
  65. *
  66. * @function encrypt
  67. * @memberof ElGamalEncrypter
  68. * @param {ZpGroupElement[]|string[]}
  69. * elements The Zp group elements to encrypt. The Zp group
  70. * elements may also be provided in the form of BigInteger string
  71. * values.
  72. * @param {Object}
  73. * [options] An object containing optional arguments.
  74. * @param {ElGamalEncryptedElements}
  75. * [options.preComputation=Generated internally] A
  76. * pre-computation of the ElGamal encryption.
  77. * @param {boolean}
  78. * [options.useShortExponent=false] If <code>true</code>, then
  79. * a <code>short</code> secret exponent will be generated for
  80. * the pre-computation process.
  81. * @param {boolean}
  82. * [options.saveSecret=false] If <code>true</code>, the secret
  83. * exponent will be saved and returned, along with the encrypted
  84. * elements. <b>CAUTION:</b> Use only when the secret is really
  85. * needed later (e.g. for zero-knowledge proof generation).
  86. * @returns {ElGamalEncryptedElements} The encrypted Zp group elements.
  87. * @throws {Error}
  88. * If the input data validation fails, the encrypter was not
  89. * initialized or an attempt is made to ElGamal encrypt a
  90. * non-quadratic residue Zp subgroup, using a short exponent.
  91. */
  92. this.encrypt = function(elements, options) {
  93. if (typeof publicKey_ === 'undefined') {
  94. throw new Error(
  95. 'Could not ElGamal encrypt; Encrypter has not been initialized with any ElGamal public key');
  96. }
  97. options = options || {};
  98. checkEncryptionData(publicKey_, elements, options);
  99. if (typeof elements[0] === 'string') {
  100. elements = getGroupElementsFromStrings(publicKey_.group, elements);
  101. }
  102. var preComputation;
  103. if (typeof options.preComputation !== 'undefined') {
  104. preComputation = options.preComputation;
  105. } else {
  106. preComputation = this.preCompute({
  107. useShortExponent: options.useShortExponent,
  108. saveSecret: options.saveSecret
  109. });
  110. }
  111. var compressedPreComputation =
  112. compressPreComputation(preComputation, elements.length);
  113. var gamma = compressedPreComputation.gamma;
  114. var preComputedPhis = compressedPreComputation.phis;
  115. var phis = [];
  116. for (var i = 0; i < preComputedPhis.length; i++) {
  117. phis.push(elements[i].multiply(preComputedPhis[i]));
  118. }
  119. var saveSecret = options.saveSecret || false;
  120. if (!saveSecret) {
  121. return new ElGamalEncryptedElements(gamma, phis);
  122. } else {
  123. return new ElGamalEncryptedElements(gamma, phis, preComputation.secret);
  124. }
  125. };
  126. /**
  127. * Computes the part of the ElGamal encryption that can be performed before
  128. * knowing the Zp group elements that are to be encrypted.
  129. *
  130. * @function preCompute
  131. * @memberof ElGamalEncrypter
  132. * @param {Object}
  133. * [options] An object containing optional arguments.
  134. * @param {boolean}
  135. * [options.useShortExponent=false] If <code>true</code>, then
  136. * a <code>short</code> secret exponent will be generated for
  137. * the pre-computation process.
  138. * @param {boolean}
  139. * [options.saveSecret=false] If <code>true</code>, the secret
  140. * exponent will be saved and returned, along with the encrypted
  141. * elements. <b>CAUTION:</b> Use only when the secret is really
  142. * needed later (e.g. for zero-knowledge proof generation).
  143. * @returns {ElGamalEncryptedElements} The encryption pre-computation.
  144. * @throws {Error}
  145. * If the input data validation fails or an attempt is made to
  146. * ElGamal pre-compute a non-quadratic residue Zp subgroup,
  147. * using a short exponent.
  148. */
  149. this.preCompute = function(options) {
  150. if (typeof publicKey_ === 'undefined') {
  151. throw new Error(
  152. 'Could not ElGamal pre-compute; Encrypter has not been initialized with any ElGamal public key');
  153. }
  154. options = options || {};
  155. checkPreComputationData(publicKey_, options);
  156. var group = publicKey_.group;
  157. var publicKeyElements = publicKey_.elements;
  158. var useShortExponent = options.useShortExponent || false;
  159. var secret = mathRandomGenerator_.nextExponent(group, {
  160. secureRandomGenerator: secureRandomGenerator_,
  161. useShortExponent: useShortExponent
  162. });
  163. var gamma = group.generator.exponentiate(secret);
  164. var phis = [];
  165. for (var i = 0; i < publicKeyElements.length; i++) {
  166. phis.push(publicKeyElements[i].exponentiate(secret));
  167. }
  168. var saveSecret = options.saveSecret || false;
  169. if (!saveSecret) {
  170. return new ElGamalEncryptedElements(gamma, phis);
  171. } else {
  172. return new ElGamalEncryptedElements(gamma, phis, secret);
  173. }
  174. };
  175. function checkElGamalPublicKey(publicKey, label) {
  176. validator.checkElGamalPublicKey(publicKey, label);
  177. validator.checkIsInstanceOf(
  178. publicKey, ElGamalPublicKey, 'ElGamalPublicKey', label);
  179. }
  180. function checkElGamalEncryptedElements(elements, label) {
  181. validator.checkElGamalEncryptedElements(elements, label);
  182. validator.checkIsInstanceOf(
  183. elements, ElGamalEncryptedElements, 'ElGamalEncryptedElements', label);
  184. }
  185. function checkEncryptionData(publicKey, elements, options) {
  186. validator.checkIsArray(elements, 'Zp group elements to encrypt');
  187. if (typeof elements[0] !== 'string') {
  188. validator.checkZpGroupElements(
  189. elements, 'Zp group elements to encrypt', publicKey.group);
  190. } else {
  191. validator.checkIsStringArray(
  192. elements, 'Zp group element value strings to encrypt');
  193. }
  194. var numElements = elements.length;
  195. var numPublicKeyElements = publicKey.elements.length;
  196. if (numElements > numPublicKeyElements) {
  197. throw new Error(
  198. 'Expected number of Zp group elements to encrypt to be less than or equal to number of public key elements: ' +
  199. numPublicKeyElements + ' ; Found: ' + numElements);
  200. }
  201. var preComputation = options.preComputation;
  202. if (typeof preComputation !== 'undefined') {
  203. checkElGamalEncryptedElements(
  204. preComputation, 'ElGamal encryption pre-computation');
  205. var numPreComputedPhis = preComputation.phis.length;
  206. if (numElements > numPreComputedPhis) {
  207. throw new Error(
  208. 'Expected number of elements to encrypt to be less than or equal to number of pre-computed phi elements: ' +
  209. numPreComputedPhis + ' ; Found: ' + numElements);
  210. }
  211. }
  212. }
  213. function checkPreComputationData(publicKey, options) {
  214. var useShortExponent = options.useShortExponent;
  215. if (typeof useShortExponent !== 'undefined') {
  216. validator.checkIsType(
  217. useShortExponent, 'boolean',
  218. '\"Use short exponent\" flag for pre-computation');
  219. if (useShortExponent && !publicKey.group.isQuadraticResidueGroup()) {
  220. throw new Error(
  221. 'Attempt to ElGamal pre-compute using short exponent for Zp subgroup that is not of type quadratic residue.');
  222. }
  223. }
  224. var saveSecret = options.saveSecret;
  225. if (typeof saveSecret !== 'undefined') {
  226. validator.checkIsType(
  227. saveSecret, 'boolean', '\"Save secret\" flag for pre-computation');
  228. }
  229. }
  230. function getGroupElementsFromStrings(group, elementValueStrings) {
  231. var p = group.p;
  232. var q = group.q;
  233. var groupElements = [];
  234. var groupElement;
  235. for (var i = 0; i < elementValueStrings.length; i++) {
  236. groupElement = mathService_.newZpGroupElement(
  237. p, q, new BigInteger(elementValueStrings[i]));
  238. groupElements.push(groupElement);
  239. }
  240. return groupElements;
  241. }
  242. function compressPreComputation(preComputation, numElements) {
  243. var preComputedPhis = preComputation.phis;
  244. if (preComputedPhis.length === numElements) {
  245. return preComputation;
  246. } else {
  247. var compressedPreComputedPhis =
  248. mathArrayCompressor_.compressTrailingZpGroupElements(
  249. preComputedPhis, numElements);
  250. return new ElGamalEncryptedElements(
  251. preComputation.gamma, compressedPreComputedPhis);
  252. }
  253. }
  254. }