/* * 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 = OrProofHandler; /** * @class OrProofHandler * @classdesc Encapsulates a handler for the OR zero-knowledge proof of * knowledge generation, pre-computation and verification processes. * To instantiate this object, use the method {@link * ZeroKnowledgeProofService.newOrProofHandler}. * @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 OrProofHandler(group, messageDigestService, mathematicalService) { var DEFAULT_AUXILIARY_DATA = 'OrProof'; var prover_ = new ZeroKnowledgeProofProver(messageDigestService, mathematicalService); var verifier_ = new ZeroKnowledgeProofVerifier(messageDigestService, mathematicalService); var progressCallback_; var progressPercentMinCheckInterval_; var measureProgress_ = false; var publicKey_; /** * Initializes the handler with the provided ElGamal public key. * * @function init * @memberof OrProofHandler * @param {ElGamalPublicKey} * publicKey The ElGamal public key. * @returns {OrProofHandler} A reference to this object, to facilitate * method chaining. * @throws {Error} * If the input data validation fails. */ this.init = function(publicKey) { validator.checkElGamalPublicKey( publicKey, 'ElGamal public key used for OR proof handler initialization', group); publicKey_ = publicKey; return this; }; /** * Generates an OR zero-knowledge proof of knowledge. Before using this * method, the handler must have been initialized with a public key, via the * method {@link OrProofHandler.init}. * * @function generate * @memberof OrProofHandler * @param {Exponent} * secret The secret exponent used to generate the ciphertext, * the knowledge of which must be proven. * @param {ZpGroupElement[]} * elements The array of Zp group element candidates, one of * which was encrypted to generate the ciphertext. * @param {number} * index The index in the array of Zp group elements of the * element that was encrypted to generate the ciphertext, the * knowledge of which must be proven. * @param {ElGamalEncryptedElements} * ciphertext The ciphertext. * @param {Object} * [options] An object containing optional arguments. * @param {Uint8Array|string} * [options.data=''] Auxiliary data. * @param {ZeroKnowledgeProofPreComputation} * [options.preComputation=Generated internally] A * pre-computation of the OR zero-knowledge proof of knowledge. * @returns {ZeroKnowledgeProof} The OR zero-knowledge proof of knowledge. * @throws {Error} * If the input data validation fails. */ this.generate = function(secret, elements, index, ciphertext, options) { if (typeof publicKey_ === 'undefined') { throw new Error( 'Cannot generate OR proof; Associated handler has not been initialized with any public key'); } options = options || {}; checkGenerationData(secret, elements, index, ciphertext, options); var data = options.data; if (typeof data === 'undefined') { data = DEFAULT_AUXILIARY_DATA; } var preComputation = options.preComputation; var privateValues = [secret]; var publicValues = generatePublicValues(ciphertext); if (typeof preComputation === 'undefined') { preComputation = this.preCompute(elements.length); } return prover_.prove( group, privateValues, publicValues, data, preComputation, elements, index); }; /** * Pre-computes an OR 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 a public key, via the method * {@link OrProofHandler.init}. * * @function preCompute * @memberof OrProofHandler * @param {number} * numElements The number of Zp group element candidates for * encryption. * @returns {ZeroKnowledgeProofPreComputation} The OR zero-knowledge proof * of knowledge pre-computation. */ this.preCompute = function(numElements) { if (typeof publicKey_ === 'undefined') { throw new Error( 'Cannot pre-compute OR proof; Associated handler has not been initialized with any public key'); } checkNumElements(numElements); var phiFunction = newPhiFunction(group, publicKey_, numElements); if (!measureProgress_) { return prover_.preCompute(group, phiFunction); } else { var preComputation = prover_.preCompute( group, phiFunction, newProgressMeter(phiFunction.numOutputs)); measureProgress_ = false; return preComputation; } }; /** * Verifies an OR zero-knowledge proof of knowledge. Before using this * method, the handler must have been initialized with a public key, via the * method {@link OrProofHandler.init}. * * @function verify * @memberof OrProofHandler * @param {ZeroKnowledgeProof} * proof The OR zero-knowledge proof of knowledge to verify. * @param {ZpGroupElement[]} * elements The array of Zp group element candidates, one of * which was encrypted to generate the ciphertext. * @param {ElGamalEncryptedElements} * ciphertext The ciphertext. * @param {Object} * [options] An object containing optional arguments. * @param {Uint8Array|string} * [options.data=''] Auxiliary data. It must be the same as that * used to generate the proof. * @returns {boolean} true if the OR zero-knowledge proof of * knowledge was verified, false otherwise. * @throws {Error} * If the input data validation fails. */ this.verify = function(proof, elements, ciphertext, options) { if (typeof publicKey_ === 'undefined') { throw new Error( 'Cannot verify OR proof; Associated handler has not been initialized with any public key'); } options = options || {}; checkVerificationData(proof, elements, ciphertext, options); var data = options.data; if (typeof data === 'undefined') { data = DEFAULT_AUXILIARY_DATA; } var phiFunction = newPhiFunction(group, publicKey_, elements.length); var publicValues = generatePublicValues(ciphertext); if (!measureProgress_) { return verifier_.verify( group, proof, phiFunction, publicValues, data, elements); } else { var verified = verifier_.verify( group, proof, phiFunction, publicValues, data, elements, newProgressMeter(phiFunction.numOutputs)); measureProgress_ = false; return verified; } }; /** * Indicates that a progress meter is to be used for the next OR * zero-knowledge proof of knowledge operation to be performed. * * @function measureProgress * @memberof OrProofHandler * @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 {OrProofHandler} 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, 'OR zero-knowledge proof'); progressCallback_ = callback; progressPercentMinCheckInterval_ = minCheckInterval; measureProgress_ = true; return this; }; function generatePublicValues(ciphertext) { var gamma = ciphertext.gamma; var phi = ciphertext.phis[0]; var publicValues = []; publicValues.push(gamma); publicValues.push(phi); return publicValues; } function newPhiFunction(group, publicKey, numElements) { var numInputs = numElements; var numOutputs = numElements * 2; var computationRules = generateComputationRules(numOutputs); var baseElements = generateBaseElements(group, publicKey); return new PhiFunction( numInputs, numOutputs, computationRules, baseElements); } function generateComputationRules(numOutputs) { var rules = []; var index1; var index2 = 0; for (var i = 0; i < numOutputs; i++) { index1 = 1 + (i % 2); if (i % 2 === 0) { index2++; } rules[i] = []; rules[i][0] = []; rules[i][0].push(index1); rules[i][0].push(index2); } return rules; } function generateBaseElements(group, publicKey) { var generator = group.generator; var publicKeyElement = publicKey.elements[0]; var baseElements = []; baseElements.push(generator); baseElements.push(publicKeyElement); return baseElements; } function newProgressMeter(numOutputs) { var progressMax = numOutputs; return new ProgressMeter( progressMax, progressCallback_, progressPercentMinCheckInterval_); } function checkGenerationData(secret, elements, index, ciphertext, options) { validator.checkExponent( secret, 'Secret exponent for OR proof generation', group.q); checkElements(elements, group); checkIndex(index, elements); checkCiphertext(ciphertext, group); if (typeof options.data !== 'undefined' && typeof options.data !== 'string') { validator.checkIsInstanceOf( options.data, Uint8Array, 'Uint8Array', 'Non-string auxiliary data for OR proof generation'); } if (options.preComputation) { validator.checkIsInstanceOf( options.preComputation, ZeroKnowledgeProofPreComputation, 'ZeroKnowledgeProofPreComputation', 'Pre-computation for OR proof generation'); } } function checkVerificationData(proof, elements, ciphertext, options) { validator.checkIsInstanceOf( proof, ZeroKnowledgeProof, 'ZeroKnowledgeProof', 'OR zero-knowledge proof'); checkElements(elements, group); checkCiphertext(ciphertext, group); if (typeof options.data !== 'undefined' && typeof options.data !== 'string') { validator.checkIsInstanceOf( options.data, Uint8Array, 'Uint8Array', 'Non-string auxiliary data for OR proof verification'); } } function checkElements(elements, group) { validator.checkZpGroupElements( elements, 'Zp group element candidates for encryption, for OR proof operation', group); checkNumElements(elements.length); } function checkNumElements(numElements) { validator.checkIsPositiveNumber( numElements, 'Number of OR proof Zp group element candidates for encryption, for OR proof pre-computation'); if (numElements < 2) { throw new Error( 'Expected number of Zp group element candidates for encryption, for OR proof pre-computation, to be at least 2; Found: ' + numElements); } } function checkIndex(index, elements) { validator.checkIsType( index, 'number', 'Array index of Zp group element that was encrypted, for OR proof operation'); if (index < 0) { throw new Error( 'Expected array index of Zp group element that was encrypted, to be greater than or equal to 0 ; Found: ' + index); } var numElements = elements.length; if (index >= numElements) { throw new Error( 'Expected array index of Zp group element that was encrypted, for OR proof operation, to be less than number of Zp group elements in array: ' + numElements + ' ; Found: ' + index); } } function checkCiphertext(ciphertext, group) { validator.checkElGamalEncryptedElements( ciphertext, 'Ciphertext ElGamal encrypted elements for OR proof operation', group); var numPhis = ciphertext.phis.length; if (numPhis !== 1) { throw new Error( 'Expected ciphetext used for OR proof operation to have exactly 1 phi element ; Found: ' + numPhis); } } }