/* * 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 = SimplePlaintextEqualityProofHandler; /** * @class SimplePlaintextEqualityProofHandler * @classdesc Encapsulates a handler for the simple plaintext equality * zero-knowledge proof of knowledge generation, pre-computation and * verification processes. To instantiate this object, use the method * {@link * ZeroKnowledgeProofService.newSimplePlaintextEqualityProofHandler}. * @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 SimplePlaintextEqualityProofHandler( group, messageDigestService, mathematicalService) { var NUM_PHI_INPUTS = 1; var DEFAULT_AUXILIARY_DATA = 'SimplePlaintextEqualityProof'; 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 SimplePlaintextEqualityProofHandler * @param {ElGamalPublicKey} * primaryPublicKey The primary ElGamal public key. * @param {ElGamalPublicKey} * secondaryPublicKey The secondary ElGamal public. * @returns {SimplePlaintextEqualityProofHandler} 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 simple 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 * SimplePlaintextEqualityProofHandler.init}. * * @function generate * @memberof SimplePlaintextEqualityProofHandler * @param {Exponent} * secret The secret exponent used to generate the primary and * 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='SimplePlaintextEqualityProof'] Auxiliary data. * @param {ZeroKnowledgeProofPreComputation} * [options.preComputation=Generated internally] A * pre-computation of the simple plaintext equality * zero-knowledge proof of knowledge. * @returns {ZeroKnowledgeProof} The simple plaintext equality * zero-knowledge proof of knowledge. * @throws {Error} * If the input data validation fails. */ this.generate = function( secret, primaryCiphertext, secondaryCiphertext, options) { if (typeof primaryPublicKey_ === 'undefined') { throw new Error( 'Cannot generate simple plaintext equality proof; Associated handler has not been initialized with any public keys'); } options = options || {}; checkGenerationData( secret, primaryCiphertext, secondaryCiphertext, options); var data = options.data; if (typeof data === 'undefined') { data = DEFAULT_AUXILIARY_DATA; } var preComputation = options.preComputation; var privateValues = [secret]; var publicValues = generatePublicValues(primaryCiphertext, secondaryCiphertext); if (typeof preComputation === 'undefined') { preComputation = this.preCompute(); } return prover_.prove( group, privateValues, publicValues, data, preComputation); }; /** * Pre-computes a simple 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 * SimplePlaintextEqualityProofHandler.init}. * * @function preCompute * @memberof SimplePlaintextEqualityProofHandler * @returns {ZeroKnowledgeProofPreComputation} The plaintext zero-knowledge * proof of knowledge pre-computation. */ this.preCompute = function() { if (typeof primaryPublicKey_ === 'undefined') { throw new Error( 'Cannot pre-compute simple 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 simple 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 * SimplePlaintextEqualityProofHandler.init}. * * @function verify * @memberof SimplePlaintextEqualityProofHandler * @param {ZeroKnowledgeProof} * proof The simple 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='SimplePlaintextEqualityProof'] Auxiliary data. * It must be the same as that used to generate the proof. * @returns {boolean} true if the simple 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 simple * plaintext equality zero-knowledge proof of knowledge operation to be * performed. * * @function measureProgress * @memberof SimplePlaintextEqualityProofHandler * @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 {SimplePlaintextEqualityProofHandler} 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, 'Simple 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 [secondaryCiphertext.gamma].concat(quotientElements); } function newPhiFunction(group, primaryPublicKey, secondaryPublicKey) { var numOutputs = primaryPublicKey.elements.length + 1; 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); for (var i = 1; i < numOutputs; i++) { rules[i] = []; rules[i][0] = []; rules[i][1] = []; rules[i][0].push(i + 1); rules[i][0].push(1); rules[i][1].push(i + numOutputs); rules[i][1].push(1); } return rules; } 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 generateBaseElements(group, primaryPublicKey, secondaryPublicKey) { return [group.generator].concat( primaryPublicKey.elements, invertPublicKeyElements(secondaryPublicKey)); } function newProgressMeter(primaryPublicKey) { var progressMax = (primaryPublicKey.elements.length * 2) + 1; return new ProgressMeter( progressMax, progressCallback_, progressPercentMinCheckInterval_); } function checkInitData(primaryPublicKey, secondaryPublicKey) { validator.checkElGamalPublicKey( primaryPublicKey, 'Primary ElGamal public key for simple plaintext equality proof handler initialization', group); validator.checkElGamalPublicKey( secondaryPublicKey, 'Secondary ElGamal public key for simple plaintext equality proof handler initialization', group); validator.checkArrayLengthsEqual( primaryPublicKey.elements, 'primary ElGamal public key Zp group elements for simple plaintex equality proof handler initialization', secondaryPublicKey.elements, 'secondary ElGamal public key Zp group elements'); } function checkGenerationData( secret, primaryCiphertext, secondaryCiphertext, options) { validator.checkExponent( secret, 'Secret expononent for simple plaintext equality zero-knowledge proof generation', group.q); validator.checkElGamalEncryptedElements( primaryCiphertext, 'Primary ciphertext ElGamal encrypted elements for simple plaintext equality proof generation', group); validator.checkElGamalEncryptedElements( secondaryCiphertext, 'Secondary ciphertext ElGamal encrypted elements for simple plaintext equality proof generation', group); validator.checkArrayLengthsEqual( primaryCiphertext.phis, 'primary ciphertext phi Zp group elements for simple 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 simple plaintext equality proof generation', secondaryPublicKey_.elements, 'secondary ElGamal public key Zp group elements'); if (typeof options.data !== 'undefined' && typeof options.data !== 'string') { validator.checkIsInstanceOf( options.data, Uint8Array, 'Uint8Array', 'Non-string auxiliary data for simple plaintext equality proof generation'); } if (typeof options.preComputation !== 'undefined') { validator.checkIsInstanceOf( options.preComputation, ZeroKnowledgeProofPreComputation, 'ZeroKnowledgeProofPreComputation', 'Pre-computation for simple plaintext equality proof generation'); } } function checkVerificationData( proof, primaryCiphertext, secondaryCiphertext, options) { validator.checkIsInstanceOf( proof, ZeroKnowledgeProof, 'ZeroKnowledgeProof', 'Simple plaintext equality zero-knowledge proof'); validator.checkElGamalEncryptedElements( primaryCiphertext, 'Primary ciphertext ElGamal encrypted elements for simple plaintext equality proof verification', group); validator.checkElGamalEncryptedElements( secondaryCiphertext, 'Secondary ciphertext ElGamal encrypted elements for simple plaintext equality proof verification', group); validator.checkArrayLengthsEqual( primaryCiphertext.phis, 'primary ciphertext phi Zp group elements for simple 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 simple plaintext equality proof verification', secondaryPublicKey_.elements, 'secondary ElGamal public key Zp group elements'); if (typeof options.data !== 'undefined' && typeof options.data !== 'string') { validator.checkIsInstanceOf( options.data, Uint8Array, 'Uint8Array', 'Non-string auxiliary data for simple plaintext equality proof verification'); } } }