/* * 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 ZeroKnowledgeProofProver = require('../prover'); var ZeroKnowledgeProofVerifier = require('../verifier'); var ZeroKnowledgeProof = require('../proof'); var ZeroKnowledgeProofPreComputation = require('../pre-computation'); var PhiFunction = require('../phi-function'); var ProgressMeter = require('../progress-meter'); var validator = require('../input-validator'); module.exports = PlaintextEqualityProofHandler; /** * @class PlaintextEqualityProofHandler * @classdesc Encapsulates a handler for the plaintext equality zero-knowledge * proof of knowledge generation, pre-computation and verification * processes. To instantiate this object, use the method {@link * ZeroKnowledgeProofService.newPlaintextEqualityProofHandler}. * @param {ZpSubgroup} * group The Zp subgroup to which all exponents and Zp group elements * required for the proof generation are associated or belong, * respectively. * @param {MessageDigestService} * messageDigestService The message digest service. * @param {MathematicalService} * mathematicalService The mathematical service. */ function PlaintextEqualityProofHandler( group, messageDigestService, mathematicalService) { var NUM_PHI_INPUTS = 2; var DEFAULT_AUXILIARY_DATA = 'PlaintextEqualityProof'; var mathGroupHandler_ = mathematicalService.newGroupHandler(); var prover_ = new ZeroKnowledgeProofProver(messageDigestService, mathematicalService); var verifier_ = new ZeroKnowledgeProofVerifier(messageDigestService, mathematicalService); var progressCallback_; var progressPercentMinCheckInterval_; var measureProgress_ = false; var primaryPublicKey_; var secondaryPublicKey_; /** * Initializes the handler with the provided primary and secondary ElGamal * public keys. * * @function init * @memberof PlaintextEqualityProofHandler * @param {ElGamalPublicKey} * primaryPublicKey The primary ElGamal public key. * @param {ElGamalPublicKey} * secondaryPublicKey The secondary ElGamal public key. * @returns {PlaintextEqualityProofHandler} A reference to this object, to * facilitate method chaining. * @throws {Error} * If the input data validation fails. */ this.init = function(primaryPublicKey, secondaryPublicKey) { checkInitData(primaryPublicKey, secondaryPublicKey); primaryPublicKey_ = primaryPublicKey; secondaryPublicKey_ = secondaryPublicKey; return this; }; /** * Generates a plaintext equality zero-knowledge proof of knowledge. Before * using this method, the handler must have been initialized with primary * secondary public keys, via the method * {@link PlaintextEqualityProofHandler.init}. * * @function generate * @memberof PlaintextEqualityProofHandler * @param {Exponent} * primarySecret The secret exponent used to generate the primary * ciphertext, the knowledge of which must be proven. * @param {Exponent} * secondarySecret The secret exponent used to generate the * secondary ciphertext, the knowledge of which must be proven. * @param {ElGamalEncryptedElements} * primaryCiphertext The primary ciphertext. * @param {ElGamalEncryptedElements} * secondaryCiphertext The secondary ciphertext. * @param {Object} * [options] An object containing optional arguments. * @param {Uint8Array|string} * [options.data='PlaintextEqualityProof'] Auxiliary data. * @param {ZeroKnowledgeProofPreComputation} * [options.preComputation=Generated internally] A * pre-computation of the plaintext equality zero-knowledge proof * of knowledge. * @returns {ZeroKnowledgeProof} The plaintext equality zero-knowledge proof * of knowledge. * @throws {Error} * If the input data validation fails. */ this.generate = function( primarySecret, secondarySecret, primaryCiphertext, secondaryCiphertext, options) { if (typeof primaryPublicKey_ === 'undefined') { throw new Error( 'Cannot generate plaintext equality proof; Associated handler has not been initialized with any public keys'); } options = options || {}; checkGenerationData( primarySecret, secondarySecret, primaryCiphertext, secondaryCiphertext, options); var data = options.data; if (typeof data === 'undefined') { data = DEFAULT_AUXILIARY_DATA; } var preComputation = options.preComputation; var privateValues = [primarySecret, secondarySecret]; var publicValues = generatePublicValues(primaryCiphertext, secondaryCiphertext); if (typeof preComputation === 'undefined') { preComputation = this.preCompute(); } return prover_.prove( group, privateValues, publicValues, data, preComputation); }; /** * Pre-computes a plaintext equality zero-knowledge proof of knowledge. * IMPORTANT: The same pre-computed values must not be used twice. Before * using this method, the handler must have been initialized with primary * secondary public keys, via the method {@link * PlaintextEqualityProofHandler.init}. * * @function preCompute * @memberof PlaintextEqualityProofHandler * @returns {ZeroKnowledgeProofPreComputation} The plaintext equality * zero-knowledge proof of knowledge pre-computation. */ this.preCompute = function() { if (typeof primaryPublicKey_ === 'undefined') { throw new Error( 'Cannot pre-compute plaintext equality proof; Associated handler has not been initialized with any public keys'); } var phiFunction = newPhiFunction(group, primaryPublicKey_, secondaryPublicKey_); if (!measureProgress_) { return prover_.preCompute(group, phiFunction); } else { var preComputation = prover_.preCompute( group, phiFunction, newProgressMeter(primaryPublicKey_)); measureProgress_ = false; return preComputation; } }; /** * Verifies a plaintext equality zero-knowledge proof of knowledge. Before * using this method, the handler must have been initialized with primary * secondary public keys, via the method {@link * PlaintextEqualityProofHandler.init}. * * @function verify * @memberof PlaintextEqualityProofHandler * @param {ZeroKnowledgeProof} * proof The plaintext equality zero-knowledge proof of knowledge * to verify. * @param {ElGamalEncryptedElements} * primaryCiphertext The primary ciphertext. * @param {ElGamalEncryptedElements} * secondaryCiphertext The secondary ciphertext. * @param {Object} * [options] An object containing optional arguments. * @param {Uint8Array|string} * [options.data='PlaintextEqualityProof'] Auxiliary data. It * must be the same as that used to generate the proof. * @returns {boolean} true if the plaintext equality * zero-knowledge proof of knowledge was verified, * false otherwise. * @throws {Error} * If the input data validation fails. */ this.verify = function( proof, primaryCiphertext, secondaryCiphertext, options) { if (typeof primaryPublicKey_ === 'undefined') { throw new Error( 'Cannot verify plaintext equality proof; Associated handler has not been initialized with any public keys'); } options = options || {}; checkVerificationData( proof, primaryCiphertext, secondaryCiphertext, options); var data = options.data; if (typeof data === 'undefined') { data = DEFAULT_AUXILIARY_DATA; } var phiFunction = newPhiFunction(group, primaryPublicKey_, secondaryPublicKey_); var publicValues = generatePublicValues(primaryCiphertext, secondaryCiphertext); if (!measureProgress_) { return verifier_.verify(group, proof, phiFunction, publicValues, data); } else { var verified = verifier_.verify( group, proof, phiFunction, publicValues, data, newProgressMeter(primaryPublicKey_)); measureProgress_ = false; return verified; } }; /** * Indicates that a progress meter is to be used for the next plaintext * equality zero-knowledge proof of knowledge operation to be performed. * * @function measureProgress * @memberof PlaintextEqualityProofHandler * @param {function} * callback The callback function for the progress meter. * @param {Object} * [options] An object containing optional arguments. * @param {number} * [minCheckInterval=10] The minimum interval used to check * progress, as a percentage of the expected final progress * value. * @returns {PlaintextEqualityProofHandler} A reference to this object, to * facilitate method chaining. * @throws {Error} * If the input data validation fails. */ this.measureProgress = function(callback, minCheckInterval) { validator.checkProgressMeterData( callback, minCheckInterval, 'Plaintext equality zero-knowledge proof'); progressCallback_ = callback; progressPercentMinCheckInterval_ = minCheckInterval; measureProgress_ = true; return this; }; function generatePublicValues(primaryCiphertext, secondaryCiphertext) { var quotientElements = mathGroupHandler_.divideElements( primaryCiphertext.phis, secondaryCiphertext.phis); return [primaryCiphertext.gamma].concat( [secondaryCiphertext.gamma], quotientElements); } function newPhiFunction(group, primaryPublicKey, secondaryPublicKey) { var numOutputs = primaryPublicKey.elements.length + 2; var computationRules = generateComputationRules(numOutputs); var baseElements = generateBaseElements(group, primaryPublicKey, secondaryPublicKey); return new PhiFunction( NUM_PHI_INPUTS, numOutputs, computationRules, baseElements); } function generateComputationRules(numOutputs) { var rules = []; rules[0] = []; rules[0][0] = []; rules[0][0].push(1); rules[0][0].push(1); rules[1] = []; rules[1][0] = []; rules[1][0].push(1); rules[1][0].push(2); for (var i = 2; i < numOutputs; i++) { rules[i] = []; rules[i][0] = []; rules[i][0].push(i); rules[i][0].push(1); rules[i][1] = []; rules[i][1].push(i + (numOutputs - 2)); rules[i][1].push(2); } return rules; } function generateBaseElements(group, primaryPublicKey, secondaryPublicKey) { return [group.generator].concat( primaryPublicKey.elements, invertPublicKeyElements(secondaryPublicKey)); } function invertPublicKeyElements(publicKey) { var elements = publicKey.elements; var invertedElements = []; for (var i = 0; i < elements.length; i++) { invertedElements.push(elements[i].invert()); } return invertedElements; } function newProgressMeter(primaryPublicKey) { var progressMax = (primaryPublicKey.elements.length * 2) + 2; return new ProgressMeter( progressMax, progressCallback_, progressPercentMinCheckInterval_); } function checkInitData(primaryPublicKey, secondaryPublicKey) { validator.checkElGamalPublicKey( primaryPublicKey, 'Primary ElGamal public key for plaintext equality proof handler initialization', group); validator.checkElGamalPublicKey( secondaryPublicKey, 'Secondary ElGamal public key for plaintext equality proof handler initialization', group); validator.checkArrayLengthsEqual( primaryPublicKey.elements, 'primary ElGamal public key Zp group elements for plaintext equality proof handler initialization', secondaryPublicKey.elements, 'secondary ElGamal public key Zp group elements'); } function checkGenerationData( primarySecret, secondarySecret, primaryCiphertext, secondaryCiphertext, options) { validator.checkExponent( primarySecret, 'Primary secret exponent for plaintext equality proof generation', group.q); validator.checkElGamalEncryptedElements( primaryCiphertext, 'Primary ciphertext ElGamal encrypted elements for plaintext equality proof generation', group); validator.checkExponent( secondarySecret, 'Secondary secret exponent for plaintext equality proof generation', group.q); validator.checkElGamalEncryptedElements( secondaryCiphertext, 'Secondary ciphertext ElGamal encrypted elements for plaintext equality proof generation', group); validator.checkArrayLengthsEqual( primaryCiphertext.phis, 'primary ciphertext phi Zp group elements for plaintext equality proof generation', primaryPublicKey_.elements, 'primary ElGamal public key Zp group elements'); validator.checkArrayLengthsEqual( secondaryCiphertext.phis, 'secondary ciphertext ElGamal phi Zp group elements for plaintext equality proof generation', secondaryPublicKey_.elements, 'secondary ElGamal public key Zp group elements'); if (typeof options.data !== 'undefined') { if (typeof options.data !== 'string') { validator.checkIsInstanceOf( options.data, Uint8Array, 'Uint8Array', 'Non-string auxiliary data for plaintext equality proof generation'); } } if (options.preComputation) { validator.checkIsInstanceOf( options.preComputation, ZeroKnowledgeProofPreComputation, 'ZeroKnowledgeProofPreComputation', 'Pre-computation for plaintext equality proof generation'); } } function checkVerificationData( proof, primaryCiphertext, secondaryCiphertext, options) { validator.checkIsInstanceOf( proof, ZeroKnowledgeProof, 'ZeroKnowledgeProof', 'Plaintext equality zero-knowledge proof'); validator.checkElGamalEncryptedElements( primaryCiphertext, 'Primary ciphertext ElGamal encrypted elements for plaintext equality proof verification', group); validator.checkElGamalEncryptedElements( secondaryCiphertext, 'Secondary ciphertext ElGamal encrypted elements for plaintext equality proof verification', group); validator.checkArrayLengthsEqual( primaryCiphertext.phis, 'primary ciphertext phi Zp group elements for plaintext equality proof verification', primaryPublicKey_.elements, 'primary ElGamal public key Zp group elements'); validator.checkArrayLengthsEqual( secondaryCiphertext.phis, 'secondary ciphertext ElGamal phi Zp group elements for plaintext equality proof verification', secondaryPublicKey_.elements, 'secondary ElGamal public key Zp group elements'); if (typeof options.data !== 'undefined') { if (typeof options.data !== 'string') { validator.checkIsInstanceOf( options.data, Uint8Array, 'Uint8Array', 'Non-string auxiliary data for plaintext equality proof verification'); } } } }