/* * 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 */ cryptolib.modules = cryptolib.modules || {}; cryptolib.modules.proofs = cryptolib.modules.proofs || {}; /** * Defines Zero-Knowledge Proofs (ZKPs). */ cryptolib.modules.proofs.implementation = function(box) { 'use strict'; box.proofs = box.proofs || {}; var exceptions, mathematical, utils, converters, secureRandomFactory, messageDigestFactory; var f = function(box) { exceptions = box.commons.exceptions; mathematical = box.commons.mathematical; utils = box.commons.utils; converters = new box.commons.utils.Converters(); secureRandomFactory = new box.primitives.securerandom.factory.SecureRandomFactory(); messageDigestFactory = new box.primitives.messagedigest.factory.MessageDigestFactory(); }; f.policies = { primitives: { messagedigest: { algorithm: box.policies.proofs.messagedigest.algorithm, provider: box.policies.proofs.messagedigest.provider }, secureRandom: {provider: box.policies.proofs.secureRandom.provider} } }; cryptolib( 'commons.exceptions', 'commons.mathematical', 'primitives.messagedigest', 'commons.utils', 'primitives.securerandom', f); /** * Schnorr proof generator. */ box.proofs.SchnorrProofGenerator = function(voterID, electionID, group) { checkNotEmpty('voter ID', voterID); checkNotEmpty('election ID', electionID); checkDefined('group', group); var computationRules = [[[1, 1]]]; var _voterID = voterID; var _electionID = electionID; var _progressMeter; var _numOutputs = 1; var buildListOfBaseElements = function() { var baseElements = []; baseElements.push(group.getGenerator()); return baseElements; }; var phiFunctionSchnorr = new box.proofs.PhiFunction( group, 1, _numOutputs, buildListOfBaseElements(), computationRules); var _prover = new box.proofs.Prover(group, phiFunctionSchnorr); this._buildListPublicValues = function(exponentiatedElement) { var publicList = []; publicList.push(exponentiatedElement); return publicList; }; this._getProver = function() { return _prover; }; this._buildListExponents = function(witness) { var exponentList = []; exponentList.push(witness); return exponentList; }; this._buildDataString = function() { return 'SchnorrProof:VoterID=' + _voterID + 'ElectionEventID=' + _electionID; }; this._getProgressMeter = function() { return _progressMeter; }; this.initProgressMeter = function( progressCallback, progressPercentMinCheckInterval) { _progressMeter = createProgressMeter( _numOutputs, progressCallback, progressPercentMinCheckInterval); return this; }; }; box.proofs.SchnorrProofGenerator.prototype = { preCompute: function() { return this._getProver().preCompute(1, this._getProgressMeter()); }, generate: function(exponentiatedElement, witness, preComputedValues) { checkDefined('exponentiated element', exponentiatedElement); checkDefined('witness', witness); if (!preComputedValues) { preComputedValues = this.preCompute(); } return this._getProver().prove( this._buildListPublicValues(exponentiatedElement), this._buildListExponents(witness), this._buildDataString(), preComputedValues); } }; /** * Exponentiation proof generator. */ box.proofs.ExponentiationProofGenerator = function(baseElements, group) { checkDefined('base elements', baseElements); checkDefined('group', group); var buildComputationRules = function(k) { var rules = []; for (var i = 0; i < k; i++) { rules[i] = []; rules[i][0] = []; rules[i][0].push(i + 1); rules[i][0].push(1); } return rules; }; var _numOutputs = baseElements.length; var phiFunctionExponentiation = new box.proofs.PhiFunction( group, 1, _numOutputs, baseElements, buildComputationRules(_numOutputs)); var _prover = new box.proofs.Prover(group, phiFunctionExponentiation); var _progressMeter; this._getProgressMeter = function() { return _progressMeter; }; this.initProgressMeter = function( progressCallback, progressPercentMinCheckInterval) { _progressMeter = createProgressMeter( _numOutputs, progressCallback, progressPercentMinCheckInterval); return this; }; this.check = function(exponentiatedElements) { if (exponentiatedElements.length !== _numOutputs) { throw new exceptions.CryptoLibException( 'The number of exponentiated elements must be equal to the number of base elements'); } }; this._getProver = function() { return _prover; }; }; box.proofs.ExponentiationProofGenerator.prototype = { preCompute: function() { var EXPONENT_QUANTITY = 1; return this._getProver().preCompute( EXPONENT_QUANTITY, this._getProgressMeter()); }, generate: function(exponentiatedElements, exponent, preComputedValues) { checkDefined('exponentiatedElements', exponentiatedElements); checkDefined('exponent', exponent); this.check(exponentiatedElements); var exponents = []; exponents.push(exponent); if (!preComputedValues) { preComputedValues = this.preCompute(); } return this._getProver().prove( exponentiatedElements, exponents, box.EXPONENTIATION_PROOF_AUXILIARY_DATA, preComputedValues); } }; /** * Plaintext proof generator. */ box.proofs.PlaintextProofGenerator = function(publicKey, group) { checkDefined('public key', publicKey); checkDefined('group', group); var buildListOfBaseElements = function() { var list = []; list.push(group.getGenerator()); list.addAll(publicKey.getGroupElementsArray()); return list; }; var buildComputationRules = function(k) { var rules = []; for (var i = 0; i < (k + 1); i++) { rules[i] = []; rules[i][0] = []; rules[i][0].push(i + 1); rules[i][0].push(1); } return rules; }; var _publicKeyLength = publicKey.getGroupElementsArray().length; var numOutputs = _publicKeyLength + 1; var phiFunctionPlaintext = new box.proofs.PhiFunction( group, 1, numOutputs, buildListOfBaseElements(), buildComputationRules(_publicKeyLength)); var _prover = new box.proofs.Prover(group, phiFunctionPlaintext); var _progressMeter; this._buildListPublicValues = function(ciphertext, plaintext) { var dividedCiphertext = mathematical.groupUtils.divide(ciphertext.getPhis(), plaintext); var publicValues = []; publicValues.push(ciphertext.getGamma()); publicValues.addAll(dividedCiphertext); return publicValues; }; this._getPublicKeyLength = function() { return _publicKeyLength; }; this._getProver = function() { return _prover; }; this._getProgressMeter = function() { return _progressMeter; }; this.initProgressMeter = function( progressCallback, progressPercentMinCheckInterval) { _progressMeter = createProgressMeter( _publicKeyLength + 1, progressCallback, progressPercentMinCheckInterval); return this; }; }; box.proofs.PlaintextProofGenerator.prototype = { preCompute: function() { return this._getProver().preCompute(1, this._getProgressMeter()); }, generate: function(ciphertext, plaintext, witness, preComputedValues) { checkDefined('ciphertext', ciphertext); checkDefined('plaintext', plaintext); checkDefined('witness', witness); var publicKeyLength = this._getPublicKeyLength(); var numPhis = ciphertext.getPhis().length; if (publicKeyLength !== numPhis) { throw new exceptions.CryptoLibException( 'The length of the ciphertext must be equal to the length of the public key + 1.'); } if (publicKeyLength !== plaintext.length) { throw new exceptions.CryptoLibException( 'The public key and the plaintext must have the same number of elements'); } if (!preComputedValues) { preComputedValues = this.preCompute(); } var exponents = []; exponents.push(witness); return this._getProver().prove( this._buildListPublicValues(ciphertext, plaintext), exponents, box.PLAINTEXT_PROOF_AUXILIARY_DATA, preComputedValues); } }; /** * Simple plaintext equality proof generator. */ box.proofs.SimplePlaintextEqualityProofGenerator = function( primaryPublicKey, secondaryPublicKey, group) { checkDefined('primary public key', primaryPublicKey); checkDefined('secondary public key', secondaryPublicKey); checkDefined('group', group); var _primaryPublicKey = primaryPublicKey; var _secondaryPublicKey = secondaryPublicKey; var _group = group; var buildComputationRules = function(k) { var rules = []; var firstDimensionLength = k + 1; rules[0] = []; rules[0][0] = []; rules[0][0].push(1); rules[0][0].push(1); for (var i = 1; i < firstDimensionLength; 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 + 1 + k); rules[i][1].push(1); } return rules; }; var buildSecondaryInvertedPublicKey = function() { var listInvertedElements = []; var keys = _secondaryPublicKey.getGroupElementsArray(); for (var i = 0; i < keys.length; i++) { listInvertedElements.push(keys[i].invert()); } return listInvertedElements; }; var buildListOfBaseElements = function() { var invertedSecondaryKeyValues = buildSecondaryInvertedPublicKey(); var baseElements = []; baseElements.push(group.getGenerator()); baseElements.addAll(_primaryPublicKey.getGroupElementsArray()); baseElements.addAll(invertedSecondaryKeyValues); return baseElements; }; var primaryPublicKeyLength = primaryPublicKey.getGroupElementsArray().length; var secondaryPublicKeyLength = secondaryPublicKey.getGroupElementsArray().length; if (primaryPublicKeyLength !== secondaryPublicKeyLength) { throw new exceptions.CryptoLibException( 'The number of elements in the primary and secondary public keys must be equal'); } var _progressMeter; var _numKeyElements = primaryPublicKey.getGroupElementsArray().length; var numOutputs = _numKeyElements + 1; var rules = buildComputationRules(_numKeyElements); var phiFunctionSimplePlaintextEquality = new box.proofs.PhiFunction( group, 1, numOutputs, buildListOfBaseElements(), rules); var _prover = new box.proofs.Prover(_group, phiFunctionSimplePlaintextEquality); this._buildListExponents = function(witness) { var exponentList = []; exponentList.push(witness); return exponentList; }; this._buildInputList = function(primaryCiphertext, secondaryCiphertext) { var dividedSubCiphertext = mathematical.groupUtils.divide( primaryCiphertext.getPhis(), secondaryCiphertext.getPhis()); var generatedList = []; generatedList.push(secondaryCiphertext.getGamma()); generatedList.addAll(dividedSubCiphertext); return generatedList; }; this._getProgressMeter = function() { return _progressMeter; }; this.initProgressMeter = function( progressCallback, progressPercentMinCheckInterval) { _progressMeter = createProgressMeter( _numKeyElements * 2 + 1, progressCallback, progressPercentMinCheckInterval); return this; }; this._getProver = function() { return _prover; }; this._check = function(primaryCiphertextLength) { if (primaryCiphertextLength !== (primaryPublicKeyLength + 1)) { throw new exceptions.CryptoLibException( 'The number of elements in the ciphertext must be equal to the number in the public key + 1'); } }; }; box.proofs.SimplePlaintextEqualityProofGenerator.prototype = { preCompute: function() { return this._getProver().preCompute(1, this._getProgressMeter()); }, generate: function( primaryCiphertext, secondaryCiphertext, witness, preComputedValues) { checkDefined('primary ciphertext', primaryCiphertext); checkDefined('secondary ciphertext', secondaryCiphertext); checkDefined('witness', witness); var primaryCiphertextLength = primaryCiphertext.getPhis().length + 1; var secondaryCiphertextLength = secondaryCiphertext.getPhis().length + 1; if (primaryCiphertextLength !== secondaryCiphertextLength) { throw new exceptions.CryptoLibException( 'The number of elements in the primary and secondary ciphertext must be equal'); } this._check(primaryCiphertextLength); if (!preComputedValues) { preComputedValues = this.preCompute(); } return this._getProver().prove( this._buildInputList(primaryCiphertext, secondaryCiphertext), this._buildListExponents(witness), box.SIMPLE_PLAINTEXT_EQUALITY_PROOF_AUXILIARY_DATA, preComputedValues); } }; /** * Plaintext equality proof generator. */ box.proofs.PlaintextEqualityProofGenerator = function( primaryPublicKey, secondaryPublicKey, group) { checkDefined('primary public key', primaryPublicKey); checkDefined('secondary public key', secondaryPublicKey); checkDefined('group', group); var numPrimaryPublicKeyElements = primaryPublicKey.getGroupElementsArray().length; var numSecondaryPublicKeyElements = secondaryPublicKey.getGroupElementsArray().length; if (numPrimaryPublicKeyElements !== numSecondaryPublicKeyElements) { throw new exceptions.CryptoLibException( 'The primary and secondary public keys contain different numbers of elements: ' + numPrimaryPublicKeyElements + ', ' + numSecondaryPublicKeyElements); } var _primaryPublicKey = primaryPublicKey; var _secondaryPublicKey = secondaryPublicKey; var _group = group; var _progressMeter; this._buildListExponents = function(primaryWitness, secondaryWitness) { var exponentList = []; exponentList.push(primaryWitness); exponentList.push(secondaryWitness); return exponentList; }; this._buildInputList = function(primaryCiphertext, secondaryCiphertext) { var dividedSubCiphertext = mathematical.groupUtils.divide( primaryCiphertext.getPhis(), secondaryCiphertext.getPhis()); var generatedList = []; generatedList.push(primaryCiphertext.getGamma()); generatedList.push(secondaryCiphertext.getGamma()); generatedList.addAll(dividedSubCiphertext); return generatedList; }; function buildComputationRules(k) { var firstDimensionLength = k + 2; 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 < firstDimensionLength; i++) { rules[i] = []; rules[i][0] = []; rules[i][0].push(i); rules[i][0].push(1); rules[i][1] = []; rules[i][1].push(i + k); rules[i][1].push(2); } return rules; } function buildListOfBaseElements() { var invertedSecondaryKeyValues = buildSecondaryInvertedPublicKey(); var baseElements = []; baseElements.push(group.getGenerator()); baseElements.addAll(_primaryPublicKey.getGroupElementsArray()); baseElements.addAll(invertedSecondaryKeyValues); return baseElements; } function buildSecondaryInvertedPublicKey() { var listInvertedElements = []; var keys = _secondaryPublicKey.getGroupElementsArray(); for (var i = 0; i < keys.length; i++) { listInvertedElements.push(keys[i].invert()); } return listInvertedElements; } var numKeyElements = primaryPublicKey.getGroupElementsArray().length; var _numOutputs = numKeyElements + 2; var rules = buildComputationRules(numKeyElements); var phiFunctionPlaintextEquality = new box.proofs.PhiFunction( group, 2, _numOutputs, buildListOfBaseElements(), rules); var _prover = new box.proofs.Prover(group, phiFunctionPlaintextEquality); this.getPrimaryPublicKey = function() { return _primaryPublicKey; }; this.getSecondaryPublicKey = function() { return _secondaryPublicKey; }; this.getGroup = function() { return _group; }; this._getProgressMeter = function() { return _progressMeter; }; this.initProgressMeter = function( progressCallback, progressPercentMinCheckInterval) { _progressMeter = createProgressMeter( numKeyElements * 2 + 2, progressCallback, progressPercentMinCheckInterval); return this; }; this._getProver = function() { return _prover; }; this.check = function(numPrimaryCiphertextElements) { if (numPrimaryCiphertextElements !== (numPrimaryPublicKeyElements + 1)) { throw new exceptions.CryptoLibException( 'The number of ciphertext elements is not equal to the number public key elements plus one.'); } }; }; box.proofs.PlaintextEqualityProofGenerator.prototype = { preCompute: function() { return this._getProver().preCompute(2, this._getProgressMeter()); }, generate: function( primaryCiphertext, primaryWitness, secondaryCiphertext, secondaryWitness, preComputedValues) { checkDefined('primary ciphertext', primaryCiphertext); checkDefined('primary witness', primaryWitness); checkDefined('secondary ciphertext', secondaryCiphertext); checkDefined('secondary witness', secondaryWitness); var numPrimaryCiphertextElements = primaryCiphertext.getPhis().length + 1; var numSecondaryCiphertextElements = secondaryCiphertext.getPhis().length + 1; if (numPrimaryCiphertextElements !== numSecondaryCiphertextElements) { throw new exceptions.CryptoLibException( 'The primary and secondary ciphertexts contain different numbers of elements: ' + numPrimaryCiphertextElements + ', ' + numSecondaryCiphertextElements); } this.check(numPrimaryCiphertextElements); if (!preComputedValues) { preComputedValues = this.preCompute(); } return this._getProver().prove( this._buildInputList(primaryCiphertext, secondaryCiphertext), this._buildListExponents(primaryWitness, secondaryWitness), box.PLAINTEXT_EQUALITY_PROOF_AUXILIARY_DATA, preComputedValues); } }; /** * Plaintext exponent equality proof generator. */ box.proofs.PlaintextExponentEqualityProofGenerator = function( baseElements, group) { var buildComputationRules = function(k) { var rules = []; var firstDimensionLength = k + 1; rules[0] = []; rules[0][0] = []; rules[0][0].push(1); rules[0][0].push(1); for (var i = 1; i < firstDimensionLength; 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 + 1 + k); rules[i][1].push(2); } return rules; }; checkDefined('base elements', baseElements); checkDefined('group', group); var _numBaseElements = baseElements.length; var _progressMeter; var k = (_numBaseElements - 1) / 2; var numOutputs = k + 1; var phiFunctionPlaintextExponentEquality = new box.proofs.PhiFunction( group, 2, numOutputs, baseElements, buildComputationRules(k)); var _prover = new box.proofs.Prover(group, phiFunctionPlaintextExponentEquality); this._buildListExponents = function(witness1, witness2) { var exponentList = []; exponentList.push(witness1); exponentList.push(witness2); return exponentList; }; this._buildListPublicValues = function(ciphertext) { var list = []; list.push(ciphertext.getGamma()); list.addAll(ciphertext.getPhis()); return list; }; this._getProver = function() { return _prover; }; this._getProgressMeter = function() { return _progressMeter; }; this.initProgressMeter = function( progressCallback, progressPercentMinCheckInterval) { _progressMeter = createProgressMeter( _numBaseElements, progressCallback, progressPercentMinCheckInterval); return this; }; this._checkLengthOf = function(ciphertext) { var k = ciphertext.getPhis().length; var expectedNumBaseElements = (2 * k) + 1; if (_numBaseElements !== expectedNumBaseElements) { throw new exceptions.CryptoLibException( 'The lengths of the ciphertext and the base elements do not have the expected relationship.'); } }; }; box.proofs.PlaintextExponentEqualityProofGenerator.prototype = { preCompute: function() { return this._getProver().preCompute(2, this._getProgressMeter()); }, generate: function(ciphertext, witness1, witness2, preComputedValues) { checkDefined('ciphertext', ciphertext); checkDefined('witness1', witness1); checkDefined('witness2', witness2); this._checkLengthOf(ciphertext); if (!preComputedValues) { preComputedValues = this.preCompute(); } return this._getProver().prove( this._buildListPublicValues(ciphertext), this._buildListExponents(witness1, witness2), box.PLAINTEXT_EXPONENT_EQUALITY_PROOF_AUXILIARY_DATA, preComputedValues); } }; /** * OR-proof generator. */ box.proofs.ORProofGenerator = function(publicKey, numElements, group) { checkPublicKey(publicKey); checkNumElements(numElements); checkGroup(group); var _publicKey = publicKey; var _numElements = numElements; var _group = group; var _numOutputs = _numElements * 2; var _progressMeter; function checkPublicKey(publicKey) { checkDefined('public key', publicKey); checkNotNull('public key', publicKey); var publicKeyLength = publicKey.getGroupElementsArray().length; if (publicKeyLength !== 1) { throw new exceptions.CryptoLibException( 'Expected public key length: 1 ; Found: ' + publicKeyLength); } } function checkNumElements(numElements) { checkDefined('numElements', numElements); checkNotNull('numElements', numElements); if (numElements < 2) { throw new exceptions.CryptoLibException( 'Expected number of elements to be greater than 1 ; Found: ' + numElements); } } function checkGroup(group) { checkDefined('group', group); checkNotNull('group', group); } function buildBaseElements() { var baseElements = []; baseElements.push(_group.getGenerator()); baseElements.push(_publicKey.getGroupElementsArray()[0]); return baseElements; } function buildComputationRules(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; } var phiFunctionOR = new box.proofs.PhiFunction( group, _numElements, _numOutputs, buildBaseElements(), buildComputationRules(_numOutputs)); var _prover = new box.proofs.Prover(_group, phiFunctionOR); this.getNumElements = function() { return _numElements; }; this.getProver = function() { return _prover; }; this.buildPublicValues = function(ciphertext) { var publicValues = []; publicValues.push(ciphertext.getGamma()); publicValues.push(ciphertext.getPhis()[0]); return publicValues; }; this.getProgressMeter = function() { return _progressMeter; }; this.initProgressMeter = function( progressCallback, progressPercentMinCheckInterval) { _progressMeter = createProgressMeter( _numOutputs, progressCallback, progressPercentMinCheckInterval); return this; }; }; box.proofs.ORProofGenerator.prototype = { preCompute: function() { return this.getProver().preCompute( this.getNumElements(), this.getProgressMeter()); }, generate: function( ciphertext, witness, elements, index, data, preComputedValues) { checkCiphertext(ciphertext); checkWitness(witness); checkElements(elements, this.getNumElements()); checkIndex(index, this.getNumElements()); checkDefined('data', data); function checkCiphertext(ciphertext) { checkDefined('ciphertext', ciphertext); checkNotNull('ciphertext', ciphertext); var ciphertextLength = ciphertext.getPhis().length + 1; if (ciphertextLength !== 2) { throw new exceptions.CryptoLibException( 'Expected ciphertext length: 2 ; Found: ' + ciphertextLength); } } function checkWitness(witness) { checkDefined('witness', witness); checkNotNull('witness', witness); } function checkElements(elements, numElementsExpected) { checkDefined('elements', elements); checkNotNull('elements', elements); var numElementsFound = elements.length; if (numElementsFound !== numElementsExpected) { throw new exceptions.CryptoLibException( 'Expected number of elements: ' + numElementsExpected + ' ; Found: ' + numElementsFound); } } function checkIndex(index, numElements) { checkDefined('index', index); checkNotNull('index', index); if (index < 0) { throw new exceptions.CryptoLibException( 'Expected index of encrypted element to be positive ; Found: ' + index); } if (index >= numElements) { throw new exceptions.CryptoLibException( 'Expected index of encrypted element to be less than number of elements: ' + numElements + ' ; Found: ' + index); } } var publicValues = this.buildPublicValues(ciphertext); var privateValues = []; privateValues.push(witness); if (data === null) { data = ''; } if (!preComputedValues) { preComputedValues = this.preCompute(); } return this.getProver().prove( publicValues, privateValues, data, preComputedValues, elements, index); } }; /** * Schnorr proof verifier. */ box.proofs.SchnorrProofVerifier = function( exponentiatedElement, voterID, electionID, proof, group) { checkDefined('exponentiated element', exponentiatedElement); checkNotEmpty('voter ID', voterID); checkNotEmpty('election ID', electionID); checkDefined('proof', proof); checkDefined('group', group); var _exponentiatedElement = exponentiatedElement; var _voterID = voterID; var _electionID = electionID; var _proof = proof; var _group = group; var _progressMeter; this._buildListOfBaseElements = function() { var baseElements = []; baseElements.push(_group.getGenerator()); return baseElements; }; this._buildExponentiatedElementsList = function() { var exponentiatedElements = []; exponentiatedElements.push(_exponentiatedElement); return exponentiatedElements; }; this._buildDataString = function() { return 'SchnorrProof:VoterID=' + _voterID + 'ElectionEventID=' + _electionID; }; var computationRules = [[[1, 1]]]; var _numOutputs = 1; var phiFunction = new box.proofs.PhiFunction( _group, 1, _numOutputs, this._buildListOfBaseElements(), computationRules); var _verifier = new box.proofs.Verifier(_group, phiFunction); this._getProof = function() { return _proof; }; this._getVerifier = function() { return _verifier; }; this._getProgressMeter = function() { return _progressMeter; }; this.initProgressMeter = function( progressCallback, progressPercentMinCheckInterval) { _progressMeter = createProgressMeter( _numOutputs, progressCallback, progressPercentMinCheckInterval); return this; }; }; box.proofs.SchnorrProofVerifier.prototype = { verify: function() { return this._getVerifier().verify( this._buildExponentiatedElementsList(), this._getProof(), this._buildDataString(), this._getProgressMeter()); } }; /** * Exponentiation proof verifier. */ box.proofs.ExponentiationProofVerifier = function( exponentiatedElements, baseElements, proof, group) { checkDefined('exponentiated elements', exponentiatedElements); checkDefined('base elements', baseElements); checkDefined('proof', proof); checkDefined('group', group); var _exponentiatedElements = exponentiatedElements; var _proof = proof; var _progressMeter; this._buildComputationRules = function(k) { var rules = []; for (var i = 0; i < k; i++) { rules[i] = []; rules[i][0] = []; rules[i][0].push(i + 1); rules[i][0].push(1); } return rules; }; var _numOutputs = exponentiatedElements.length; var phiFunctionExponentiation = new box.proofs.PhiFunction( group, 1, _numOutputs, baseElements, this._buildComputationRules(_numOutputs)); var _verifier = new box.proofs.Verifier(group, phiFunctionExponentiation); this._getProof = function() { return _proof; }; this.getExponentiatedElements = function() { return _exponentiatedElements; }; this._getVerifier = function() { return _verifier; }; this._getProgressMeter = function() { return _progressMeter; }; this.initProgressMeter = function( progressCallback, progressPercentMinCheckInterval) { _progressMeter = createProgressMeter( _numOutputs, progressCallback, progressPercentMinCheckInterval); return this; }; }; box.proofs.ExponentiationProofVerifier.prototype = { verify: function() { return this._getVerifier().verify( this.getExponentiatedElements(), this._getProof(), box.EXPONENTIATION_PROOF_AUXILIARY_DATA, this._getProgressMeter()); } }; /** * Plaintext proof verifier. */ box.proofs.PlaintextProofVerifier = function(publicKey, group) { checkDefined('public key', publicKey); checkDefined('group', group); var _publicKey = publicKey; var _group = group; var _progressMeter; this._buildListOfBaseElements = function() { var list = []; list.push(_group.getGenerator()); list.addAll(_publicKey.getGroupElementsArray()); return list; }; this._buildListPublicValues = function(ciphertext, plaintext) { var dividedCiphertext = mathematical.groupUtils.divide(ciphertext.getPhis(), plaintext); var publicValues = []; publicValues.push(ciphertext.getGamma()); publicValues.addAll(dividedCiphertext); return publicValues; }; this._buildComputationRules = function(k) { var rules = []; for (var i = 0; i < (k + 1); i++) { rules[i] = []; rules[i][0] = []; rules[i][0].push(i + 1); rules[i][0].push(1); } return rules; }; var _publicKeyLength = _publicKey.getGroupElementsArray().length; var numOutputs = _publicKeyLength + 1; var phiFunctionPlaintext = new box.proofs.PhiFunction( _group, 1, numOutputs, this._buildListOfBaseElements(), this._buildComputationRules(_publicKeyLength)); var _verifier = new box.proofs.Verifier(_group, phiFunctionPlaintext); this.getPublicKey = function() { return _publicKey; }; this.getGroup = function() { return _group; }; this._getVerifier = function() { return _verifier; }; this._getProgressMeter = function() { return _progressMeter; }; this.initProgressMeter = function( progressCallback, progressPercentMinCheckInterval) { _progressMeter = createProgressMeter( _publicKeyLength + 1, progressCallback, progressPercentMinCheckInterval); return this; }; }; box.proofs.PlaintextProofVerifier.prototype = { verify: function(ciphertext, plaintext, proof) { checkDefined('ciphertext', ciphertext); checkDefined('plaintext', plaintext); checkDefined('proof', proof); return this._getVerifier().verify( this._buildListPublicValues(ciphertext, plaintext), proof, box.PLAINTEXT_PROOF_AUXILIARY_DATA, this._getProgressMeter()); } }; /** * Simple plaintext equality proof verifier. */ box.proofs.SimplePlaintextEqualityProofVerifier = function( primaryCiphertext, primaryPublicKey, secondaryCiphertext, secondaryPublicKey, proof, group) { checkDefined('primary ciphertext', primaryCiphertext); checkDefined('primary public key', primaryPublicKey); checkDefined('secondary ciphertext', secondaryCiphertext); checkDefined('secondary public key', secondaryPublicKey); checkDefined('proof', proof); checkDefined('group', group); var numPrimaryCiphertextElements = primaryCiphertext.getPhis().length + 1; var numPrimaryPublicKeyElements = primaryPublicKey.getGroupElementsArray().length; var numSecondaryCiphertextElements = secondaryCiphertext.getPhis().length + 1; var numSecondaryPublicKeyElements = secondaryPublicKey.getGroupElementsArray().length; if (numPrimaryCiphertextElements !== numSecondaryCiphertextElements) { throw new exceptions.CryptoLibException( 'The primary and secondary ciphertexts contain different numbers of elements: ' + numPrimaryCiphertextElements + ', ' + numSecondaryCiphertextElements); } if (numPrimaryPublicKeyElements !== numSecondaryPublicKeyElements) { throw new exceptions.CryptoLibException( 'The primary and secondary public keys contain different numbers of elements: ' + numPrimaryPublicKeyElements + ', ' + numSecondaryPublicKeyElements); } if (numPrimaryCiphertextElements !== (numPrimaryPublicKeyElements + 1)) { throw new exceptions.CryptoLibException( 'The number of ciphertext elements is not equal to the number public key elements plus one.'); } var _primaryCiphertext = primaryCiphertext; var _primaryPublicKey = primaryPublicKey; var _secondaryCiphertext = secondaryCiphertext; var _secondaryPublicKey = secondaryPublicKey; var _proof = proof; var _progressMeter; function buildComputationRules(k) { var rules = []; var firstDimensionLength = k + 1; rules[0] = []; rules[0][0] = []; rules[0][0].push(1); rules[0][0].push(1); for (var i = 1; i < firstDimensionLength; 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 + 1 + k); rules[i][1].push(1); } return rules; } function buildListOfBaseElements() { var invertedSecondaryKeyValues = buildSecondaryInvertedPublicKey(); var baseElements = []; baseElements.push(group.getGenerator()); baseElements.addAll(_primaryPublicKey.getGroupElementsArray()); baseElements.addAll(invertedSecondaryKeyValues); return baseElements; } function buildSecondaryInvertedPublicKey() { var listInvertedElements = []; var keys = _secondaryPublicKey.getGroupElementsArray(); for (var i = 0; i < keys.length; i++) { listInvertedElements.push(keys[i].invert()); } return listInvertedElements; } var _primarySubCiphertext = _primaryCiphertext.getPhis(); var _secondarySubCiphertext = _secondaryCiphertext.getPhis(); var _numKeyElements = _primaryPublicKey.getGroupElementsArray().length; var numOutputs = _numKeyElements + 1; var phiFunctionSimplePlaintextEquality = new box.proofs.PhiFunction( group, 1, numOutputs, buildListOfBaseElements(), buildComputationRules(_numKeyElements)); var _verifier = new box.proofs.Verifier(group, phiFunctionSimplePlaintextEquality); this._buildInputList = function() { var dividedSubCiphertext = mathematical.groupUtils.divide( _primarySubCiphertext, _secondarySubCiphertext); var generatedList = []; generatedList.push(_primaryCiphertext.getGamma()); generatedList.addAll(dividedSubCiphertext); return generatedList; }; this._getProof = function() { return _proof; }; this._getProgressMeter = function() { return _progressMeter; }; this.initProgressMeter = function( progressCallback, progressPercentMinCheckInterval) { _progressMeter = createProgressMeter( _numKeyElements * 2 + 1, progressCallback, progressPercentMinCheckInterval); return this; }; this._getVerifier = function() { return _verifier; }; }; box.proofs.SimplePlaintextEqualityProofVerifier.prototype = { verify: function() { return this._getVerifier().verify( this._buildInputList(), this._getProof(), box.SIMPLE_PLAINTEXT_EQUALITY_PROOF_AUXILIARY_DATA, this._getProgressMeter()); } }; /** * Plaintext equality proof verifier. */ box.proofs.PlaintextEqualityProofVerifier = function( primaryCiphertext, primaryPublicKey, secondaryCiphertext, secondaryPublicKey, proof, group) { checkDefined('primary ciphertext', primaryCiphertext); checkDefined('primary public key', primaryPublicKey); checkDefined('secondary ciphertext', secondaryCiphertext); checkDefined('secondary public key', secondaryPublicKey); checkDefined('proof', proof); checkDefined('group', group); var _primaryCiphertext = primaryCiphertext; var _primaryPublicKey = primaryPublicKey; var _secondaryCiphertext = secondaryCiphertext; var _secondaryPublicKey = secondaryPublicKey; var _proof = proof; var buildComputationRules = function(k) { var firstDimensionLength = k + 2; 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 < firstDimensionLength; i++) { rules[i] = []; rules[i][0] = []; rules[i][1] = []; rules[i][0].push(i); rules[i][0].push(1); rules[i][1].push(i + k); rules[i][1].push(2); } return rules; }; function buildListOfBaseElements() { var invertedSecondaryKeyValues = buildSecondaryInvertedPublicKey(); var baseElements = []; baseElements.push(group.getGenerator()); baseElements.addAll(_primaryPublicKey.getGroupElementsArray()); baseElements.addAll(invertedSecondaryKeyValues); return baseElements; } function buildSecondaryInvertedPublicKey() { var listInvertedElements = []; var keys = _secondaryPublicKey.getGroupElementsArray(); for (var i = 0; i < keys.length; i++) { listInvertedElements.push(keys[i].invert()); } return listInvertedElements; } var _progressMeter; var _primarySubCiphertext = _primaryCiphertext.getPhis(); var _secondarySubCiphertext = _secondaryCiphertext.getPhis(); var _numKeyElements = primaryPublicKey.getGroupElementsArray().length; var numOutputs = _numKeyElements + 2; var phiFunctionPlaintextEquality = new box.proofs.PhiFunction( group, 2, numOutputs, buildListOfBaseElements(), buildComputationRules(_numKeyElements)); var _verifier = new box.proofs.Verifier(group, phiFunctionPlaintextEquality); this._buildInputList = function() { var dividedSubCiphertext = mathematical.groupUtils.divide( _primarySubCiphertext, _secondarySubCiphertext); var generatedList = []; generatedList.push(_primaryCiphertext.getGamma()); generatedList.push(_secondaryCiphertext.getGamma()); generatedList.addAll(dividedSubCiphertext); return generatedList; }; this._getProof = function() { return _proof; }; this._getProgressMeter = function() { return _progressMeter; }; this.initProgressMeter = function( progressCallback, progressPercentMinCheckInterval) { _progressMeter = createProgressMeter( _numKeyElements * 2 + 2, progressCallback, progressPercentMinCheckInterval); return this; }; this._getVerifier = function() { return _verifier; }; }; box.proofs.PlaintextEqualityProofVerifier.prototype = { verify: function() { return this._getVerifier().verify( this._buildInputList(), this._getProof(), box.PLAINTEXT_EQUALITY_PROOF_AUXILIARY_DATA, this._getProgressMeter()); } }; /** * Plaintext exponent equality proof verifier. */ box.proofs.PlaintextExponentEqualityProofVerifier = function( ciphertext, baseElements, proof, group) { checkDefined('ciphertext', ciphertext); checkDefined('base elements', baseElements); checkDefined('proof', proof); checkDefined('group', group); var _ciphertext = ciphertext; var _baseElements = baseElements; var _proof = proof; var _group = group; var _progressMeter; this._buildComputationRules = function(k) { var rules = []; var firstDimensionLength = k + 1; rules[0] = []; rules[0][0] = []; rules[0][0].push(1); rules[0][0].push(1); for (var i = 1; i < firstDimensionLength; 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 + 1 + k); rules[i][1].push(2); } return rules; }; this._buildListPublicValues = function() { var list = []; list.push(_ciphertext.getGamma()); list.addAll(_ciphertext.getPhis()); return list; }; var _k = _ciphertext.getPhis().length; var numOutputs = _k + 1; var phiFunctionPlaintextExponentEquality = new box.proofs.PhiFunction( _group, 2, numOutputs, _baseElements, this._buildComputationRules(_k)); var _verifier = new box.proofs.Verifier(_group, phiFunctionPlaintextExponentEquality); this._getProof = function() { return _proof; }; this._getVerifier = function() { return _verifier; }; this._getProgressMeter = function() { return _progressMeter; }; this.initProgressMeter = function( progressCallback, progressPercentMinCheckInterval) { _progressMeter = createProgressMeter( _k * 2 + 1, progressCallback, progressPercentMinCheckInterval); return this; }; }; box.proofs.PlaintextExponentEqualityProofVerifier.prototype = { verify: function() { return this._getVerifier().verify( this._buildListPublicValues(), this._getProof(), box.PLAINTEXT_EXPONENT_EQUALITY_PROOF_AUXILIARY_DATA, this._getProgressMeter()); } }; /** * OR-proof verifier. */ box.proofs.ORProofVerifier = function(publicKey, numElements, group) { checkPublicKey(publicKey); checkNumElements(numElements); checkGroup(group); var _publicKey = publicKey; var _numElements = numElements; var _group = group; var _numOutputs = _numElements * 2; var _progressMeter; function checkPublicKey(publicKey) { checkDefined('public key', publicKey); checkNotNull('public key', publicKey); var publicKeyLength = publicKey.getGroupElementsArray().length; if (publicKeyLength !== 1) { throw new exceptions.CryptoLibException( 'Expected public key length: 1 ; Found: ' + publicKeyLength); } } function checkNumElements(numElements) { checkDefined('numElements', numElements); checkNotNull('numElements', numElements); if (numElements < 2) { throw new exceptions.CryptoLibException( 'Expected number of elements to be greater than 1 ; Found: ' + numElements); } } function checkGroup(group) { checkDefined('group', group); checkNotNull('group', group); } function buildBaseElements() { var baseElements = []; baseElements.push(_group.getGenerator()); baseElements.push(_publicKey.getGroupElementsArray()[0]); return baseElements; } function buildComputationRules(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; } var phiFunctionOR = new box.proofs.PhiFunction( _group, _numElements, _numOutputs, buildBaseElements(), buildComputationRules(_numOutputs)); var _verifier = new box.proofs.Verifier(_group, phiFunctionOR); this.buildPublicValues = function(ciphertext) { var publicValues = []; publicValues.push(ciphertext.getGamma()); publicValues.push(ciphertext.getPhis()[0]); return publicValues; }; this.getNumElements = function() { return _numElements; }; this.getVerifier = function() { return _verifier; }; this.getProgressMeter = function() { return _progressMeter; }; this.initProgressMeter = function( progressCallback, progressPercentMinCheckInterval) { _progressMeter = createProgressMeter( _numOutputs, progressCallback, progressPercentMinCheckInterval); return this; }; }; box.proofs.ORProofVerifier.prototype = { verify: function(ciphertext, elements, data, proof) { checkCiphertext(ciphertext); checkElements(elements, this.getNumElements()); checkDefined('data', data); checkProof(proof); function checkCiphertext(ciphertext) { checkDefined('ciphertext', ciphertext); checkNotNull('ciphertext', ciphertext); var ciphertextLength = ciphertext.getPhis().length + 1; if (ciphertextLength !== 2) { throw new exceptions.CryptoLibException( 'Expected ciphertext length: 2 ; Found: ' + ciphertextLength); } } function checkElements(elements, numElementsExpected) { checkDefined('elements', elements); checkNotNull('elements', elements); var numElementsFound = elements.length; if (numElementsFound !== numElementsExpected) { throw new exceptions.CryptoLibException( 'Expected number of elements: ' + numElementsExpected + ' ; Found: ' + numElementsFound); } } function checkProof(proof) { checkDefined('proof', proof); checkNotNull('proof', proof); } if (data === null) { data = ''; } return this.getVerifier().verify( this.buildPublicValues(ciphertext), proof, data, elements, this.getProgressMeter()); } }; /** * Proofs factory, can be used for creating proof generators and verifiers. */ box.proofs.ProofsFactory = function() { }; box.proofs.ProofsFactory.prototype = { createSchnorrProofGenerator: function(voterID, electionID, group) { return new box.proofs.SchnorrProofGenerator(voterID, electionID, group); }, createExponentiationProofGenerator: function(baseElements, group) { return new box.proofs.ExponentiationProofGenerator(baseElements, group); }, createPlaintextProofGenerator: function(publicKey, group) { return new box.proofs.PlaintextProofGenerator(publicKey, group); }, createSimplePlaintextEqualityProofGenerator: function( primaryPublicKey, secondaryPublicKey, group) { return new box.proofs.SimplePlaintextEqualityProofGenerator( primaryPublicKey, secondaryPublicKey, group); }, createPlaintextEqualityProofGenerator: function( primaryPublicKey, secondaryPublicKey, group) { return new box.proofs.PlaintextEqualityProofGenerator( primaryPublicKey, secondaryPublicKey, group); }, createPlaintextExponentEqualityProofGenerator: function( baseElements, group) { return new box.proofs.PlaintextExponentEqualityProofGenerator( baseElements, group); }, createORProofGenerator: function(publicKey, numElements, group) { return new box.proofs.ORProofGenerator(publicKey, numElements, group); }, createSchnorrProofVerifier: function( element, voterID, electionEventID, proof, group) { return new box.proofs.SchnorrProofVerifier( element, voterID, electionEventID, proof, group); }, createExponentiationProofVerifier: function( exponentiatedElements, baseElements, proof, group) { return new box.proofs.ExponentiationProofVerifier( exponentiatedElements, baseElements, proof, group); }, createPlaintextProofVerifier: function( publicKey, ciphertext, plaintext, proof, group) { return new box.proofs.PlaintextProofVerifier( publicKey, ciphertext, plaintext, proof, group); }, createSimplePlaintextEqualityProofVerifier: function( primaryCiphertext, primaryPublicKey, secondaryCiphertext, secondaryPublicKey, proof, group) { return new box.proofs.SimplePlaintextEqualityProofVerifier( primaryCiphertext, primaryPublicKey, secondaryCiphertext, secondaryPublicKey, proof, group); }, createPlaintextEqualityProofVerifier: function( primaryCiphertext, primaryPublicKey, secondaryCiphertext, secondaryPublicKey, proof, group) { return new box.proofs.PlaintextEqualityProofVerifier( primaryCiphertext, primaryPublicKey, secondaryCiphertext, secondaryPublicKey, proof, group); }, createPlaintextExponentEqualityProofVerifier: function( ciphertext, baseElements, proof, group) { return new box.proofs.PlaintextExponentEqualityProofVerifier( ciphertext, baseElements, proof, group); }, createORProofVerifier: function(publicKey, numElements, group) { return new box.proofs.ORProofVerifier(publicKey, numElements, group); } }; /** * Represents the PHI function of Maurer's Unified Zero-Knowledge Proof of * Knowledge (ZK-PoK) scheme. */ box.proofs.PhiFunction = function( group, numInputs, numOutputs, baseElements, computationRules) { var validateComputationRules = function( numInputs, numOutputs, baseElements, computationRules) { // Validate that both of the values in each of the // pairs of rules have values in the correct ranges var numBaseElements = baseElements.length; for (var i = 0; i < numOutputs; i++) { for (var j = 0; j < computationRules[i].length; j++) { // Validate the first value of the pair var pairValue1 = computationRules[i][j][0]; if ((pairValue1 < 1) || (pairValue1 > numBaseElements)) { throw new exceptions.CryptoLibException( 'Invalid first value in index pair - should be in the range 1 to ' + numBaseElements + ', but it was ' + pairValue1); } // Validate the second value of the pair var pairValue2 = computationRules[i][j][1]; if ((pairValue2 < 1) || (pairValue2 > numInputs)) { throw new exceptions.CryptoLibException( 'Invalid second value in index pair - should be in the range 1 to ' + numInputs + ', but it was ' + pairValue2); } } } }; checkDefined('group', group); checkIsPositive('number of inputs', numInputs); checkIsPositive('number of outputs', numOutputs); checkInitializedArray('base elements', baseElements); checkDefined('computation rules', computationRules); validateComputationRules( numInputs, numOutputs, baseElements, computationRules); var _numInputs = numInputs; var _numOutputs = numOutputs; var _baseElements = baseElements; var _computationRules = computationRules; /** * @return The number of inputs */ this.getNumInputs = function() { return _numInputs; }; /** * @return The number of outputs */ this.getNumOutputs = function() { return _numOutputs; }; /** * @return The list of base elements */ this.getBaseElements = function() { return _baseElements; }; /** * @return The computation rules. */ this.getComputationRules = function() { return _computationRules; }; }; box.proofs.PhiFunction.prototype = { calculatePhi: function(inputs, progressMeter) { checkDefined('inputs', inputs); if (progressMeter === undefined) { progressMeter = new utils.ProgressMeter(); } checkDefined('progressMeter', progressMeter); var computationsRules = this.getComputationRules(); var baseElements = this.getBaseElements(); var partialResult; var resultForThisListOfPairs; var result = []; var progress = 0; for (var i = 0; i < computationsRules.length; i++) { var numPairsInList = computationsRules[i].length; resultForThisListOfPairs = baseElements[computationsRules[i][0][0] - 1].exponentiate( inputs[computationsRules[i][0][1] - 1]); progressMeter.update(++progress); for (var j = 1; j < numPairsInList; j++) { var index1 = computationsRules[i][j][0]; index1 = index1 - 1; var index2 = computationsRules[i][j][1]; index2 = index2 - 1; partialResult = baseElements[index1].exponentiate(inputs[index2]); resultForThisListOfPairs = resultForThisListOfPairs.multiply(partialResult); progressMeter.update(++progress); } result.push(resultForThisListOfPairs); } return result; } }; /** * Acts as a prover in a Zero-Knowledge Proof of Knowledge (ZK-PoK) exchange * using Maurer's unified PHI function. */ box.proofs.Prover = function(group, phiFunction) { checkDefined('group', group); checkDefined('phiFunction', phiFunction); var _group = group; var _phiFunction = phiFunction; var _cryptoRandomInteger = secureRandomFactory.getCryptoRandomInteger(); /** * @return Get the group. */ this.getGroup = function() { return _group; }; /** * @return Get the phi function. */ this.getPhiFunction = function() { return _phiFunction; }; this._calculateRandomExponents = function(numExponentsNeeded) { var exponentsArray = []; for (var i = 0; i < numExponentsNeeded; i++) { var randomExponent = mathematical.groupUtils.generateRandomExponent( _group, _cryptoRandomInteger); exponentsArray.push(randomExponent); } return exponentsArray; }; this._generateHash = function(publicValues, phiOutputs, data) { var hashBuilder = new box.proofs.HashBuilder(); var hashB64 = hashBuilder.buildHashForProof(publicValues, phiOutputs, data); var hash = converters.base64Decode(hashB64); var hashBytes = converters.bytesFromString(hash); hashBytes.unshift(0); var value = new box.forge.jsbn.BigInteger(hashBytes); return new mathematical.Exponent(_group.getQ(), value); }; this._generateValuesList = function( privateValues, hash, randomExponents, index) { var proofValues = []; var proofValue; if (typeof index === 'undefined') { for (var i = 0; i < privateValues.length; i++) { proofValue = randomExponents[i].getValue().add( privateValues[i].getValue().multiply(hash.getValue())); proofValues.push( new mathematical.Exponent(_group.getQ(), proofValue)); } } else { var numElements = randomExponents.length / 2; var updatedHash = hash; for (var j = 0; j < numElements; j++) { if (j === index) { for (var k = 0; k < numElements; k++) { if (k !== index) { updatedHash = updatedHash.subtract(randomExponents[k]); } } proofValues.push(updatedHash); } else { proofValues.push(randomExponents[j]); } } for (var m = numElements; m < (numElements * 2); m++) { if (m === (numElements + index)) { proofValue = randomExponents[m].getValue().add( privateValues[0].getValue().multiply(updatedHash.getValue())); proofValues.push( new mathematical.Exponent(_group.getQ(), proofValue)); } else { proofValues.push(randomExponents[m]); } } } return proofValues; }; this._updatePreComputedValues = function( preComputedValues, publicValues, elements, index) { var gamma = publicValues[0]; var phi = publicValues[1]; var numElements = elements.length; var challenges = this._calculateRandomExponents(numElements); var updatedExponents = []; updatedExponents.addAll(challenges); updatedExponents.addAll(preComputedValues.getExponents()); var updatedPhiOutputs = []; var phiOutputs = preComputedValues.getPhiOutputs(); for (var j = 0; j < numElements; j++) { var offset = 2 * j; if (j === index) { updatedPhiOutputs.push(phiOutputs[offset]); updatedPhiOutputs.push(phiOutputs[offset + 1]); } else { var gammaFactor = gamma.invert().exponentiate(challenges[j]); var phiFactor = (phi.invert().multiply(elements[j])).exponentiate(challenges[j]); updatedPhiOutputs.push(phiOutputs[offset].multiply(gammaFactor)); updatedPhiOutputs.push(phiOutputs[offset + 1].multiply(phiFactor)); } } return new box.proofs.ProofPreComputedValues( updatedExponents, updatedPhiOutputs); }; }; box.proofs.Prover.prototype = { /** * Pre-compute time-consuming values, so they can be used later for * method prove(). */ preCompute: function(privateValuesLength, progressMeter) { var randomExponents = this._calculateRandomExponents(privateValuesLength); var phiOutputs = this.getPhiFunction().calculatePhi(randomExponents, progressMeter); return new box.proofs.ProofPreComputedValues(randomExponents, phiOutputs); }, /** * Performs prover steps in a ZK-PoK exchange using Maurer's unified PHI * function. This involves generating a proof, using the received inputs * and this Prover's internal fields. */ prove: function( publicValues, privateValues, data, preComputedValues, elements, index) { checkDefined('public values', publicValues); checkDefined('private values', privateValues); checkDefined('data', data); if (!preComputedValues || typeof preComputedValues.getPhiOutputs() === 'undefined' || typeof preComputedValues.getExponents() === 'undefined') { throw new exceptions.CryptoLibException( 'The precomputed values must be initialized'); } if (typeof elements !== 'undefined') { preComputedValues = this._updatePreComputedValues( preComputedValues, publicValues, elements, index); } var hash = this._generateHash( publicValues, preComputedValues.getPhiOutputs(), data); var proofValues = this._generateValuesList( privateValues, hash, preComputedValues.getExponents(), index); return new box.proofs.Proof(hash, proofValues); } }; /** * Acts as a verifier in a Zero-Knowledge Proof of Knowledge (ZK-PoK) * exchange using Maurer's unified PHI function. */ box.proofs.Verifier = function(group, phiFunction) { checkDefined('group', group); checkDefined('phiFunction', phiFunction); var _group = group; var _phiFunction = phiFunction; /** * @return Get the group. */ this.getGroup = function() { return _group; }; /** * @return Get the phi function. */ this.getPhiFunction = function() { return _phiFunction; }; this._calculateComputedValues = function( publicValues, phiOutputs, proofHashValueOrElements, challenges) { var proofHashValue; var elements; if (!isArray(proofHashValueOrElements)) { proofHashValue = proofHashValueOrElements; } else { elements = proofHashValueOrElements; } var computedValues = []; if (typeof proofHashValue !== 'undefined') { for (var i = 0; i < phiOutputs.length; i++) { computedValues.push(phiOutputs[i].multiply( publicValues[i].exponentiate(proofHashValue.negate()))); } } else { var gamma = publicValues[0]; var phi = publicValues[1]; for (var j = 0; j < challenges.length; j++) { var offset = 2 * j; var gammaFactor = gamma.invert().exponentiate(challenges[j]); var phiFactor = (phi.invert().multiply(elements[j])).exponentiate(challenges[j]); computedValues.push(phiOutputs[offset].multiply(gammaFactor)); computedValues.push(phiOutputs[offset + 1].multiply(phiFactor)); } } return computedValues; }; this._calculateHash = function(publicValues, computedValues, data) { var hashBuilder = new box.proofs.HashBuilder(); var hashB64 = hashBuilder.buildHashForProof(publicValues, computedValues, data); var hash = converters.base64Decode(hashB64); var hashBytes = converters.bytesFromString(hash); hashBytes.unshift(0); var value = new box.forge.jsbn.BigInteger(hashBytes); return new mathematical.Exponent(_group.getQ(), value); }; }; box.proofs.Verifier.prototype = { /** * Verifies the received input as being true or false. */ verify: function( publicValues, proof, data, elementsOrProgressMeter, progressMeter) { checkDefined('public values', publicValues); checkDefined('proof', proof); checkDefined('data', data); var elements; if (!isArray(elementsOrProgressMeter)) { progressMeter = elementsOrProgressMeter; } else { elements = elementsOrProgressMeter; } var proofHashValue = proof.getHash(); var phiOutputs; var computedValues; if (typeof elements === 'undefined') { phiOutputs = this.getPhiFunction().calculatePhi( proof.getValues(), progressMeter); computedValues = this._calculateComputedValues( publicValues, phiOutputs, proofHashValue); } else { var numElements = elements.length; var proofValues = proof.getValues(); var challenges = proofValues.slice(0, numElements); var phiInputs = proofValues.slice(numElements, numElements * 2); phiOutputs = this.getPhiFunction().calculatePhi(phiInputs, progressMeter); computedValues = this._calculateComputedValues( publicValues, phiOutputs, elements, challenges); } var calculatedHash = this._calculateHash(publicValues, computedValues, data); return proofHashValue.equals(calculatedHash); } }; /** * @class ProofPreComputedValues stores randomly created exponents and * outputs of box.proofs.PhiFunction.calculatePhi(). */ box.proofs.ProofPreComputedValues = function(exponents, phiOutputs) { this.className = 'ProofPreComputedValues'; var _exponents = exponents; var _phiOutputs = phiOutputs; /** * @return the exponents. */ this.getExponents = function() { return _exponents; }; /** * @return the phi function output. */ this.getPhiOutputs = function() { return _phiOutputs; }; }; box.proofs.ProofPreComputedValues.prototype = { stringify: function() { var exponentValues = Array(); var phiOutputValues = Array(); for (var i = 0; i < this.getExponents().length; i++) { exponentValues[i] = converters.base64FromBigInteger(this.getExponents()[i].getValue()); } for (var j = 0; j < this.getPhiOutputs().length; j++) { phiOutputValues[j] = converters.base64FromBigInteger( this.getPhiOutputs()[j].getElementValue()); } return JSON.stringify({ preComputed: { p: converters.base64FromBigInteger(this.getPhiOutputs()[0].getP()), q: converters.base64FromBigInteger(this.getPhiOutputs()[0].getQ()), exponents: exponentValues, phiOutputs: phiOutputValues } }); } }; /** * Represents a proof in a ZKP exchange. */ box.proofs.Proof = function(hashValue, values) { checkDefined('hash value', hashValue); checkDefined('values', values); var _hashValue = hashValue; var _values = values; /** * @return Get the hash value of this proof. */ this.getHash = function() { return _hashValue; }; /** * @return Get the list of values of this proof. */ this.getValues = function() { return _values; }; }; box.proofs.Proof.prototype = { /** * @return true if the given proof has the same hash and values. */ equals: function(proof) { return proof.getHash().equals(this.getHash()) && proof.getValues().equals(this.getValues()); }, /** * Displays a string representation of this proof JSON. *

* Note: in order to permit interoperability between libraries, this * representation should match the equivalent representation in Java. * * @function * @returns a string representation of this proof JSON. */ stringify: function() { var values = Array(); for (var i = 0; i < this.getValues().length; i++) { values[i] = converters.base64FromBigInteger(this.getValues()[i].getValue()); } return JSON.stringify({ zkProof: { q: converters.base64FromBigInteger(this.getHash().getQ()), hash: converters.base64FromBigInteger(this.getHash().getValue()), values: values } }); } }; /** * A class which provides utility methods for generating a hash from a set * of inputs. */ box.proofs.HashBuilder = function() { /** * Convert an integer to a byte array */ this._getInt64Bytes = function(x) { var bytes = []; var i = 8; do { bytes[--i] = x & (255); x = x >> 8; } while (i); return bytes; }; /** * Converts an array to a String. */ this._arrayElementValuesToString = function(groupElements) { var str = ''; for (var i = 0; i < groupElements.length; i++) { if (groupElements[i].getElementValue() !== undefined) { str += groupElements[i].getElementValue(); } else { throw new exceptions.CryptoLibException( 'Group Element must implement getElementValue() method.'); } } return str; }; }; box.proofs.HashBuilder.prototype = { /** * Generates a hash value based on the element values of publicValues * and generatedValues and the data. * * @function * @param publicValues * {array} group elements representing public values. * @param generatedValues * {array} group elements representing generated values. * @param data * {string} some data encoded as a String. * @returns a generated hash code. * @throws CryptoLibException * if any of the received objects has unsupported type. */ buildHashForProof: function(publicValues, generatedValues, data) { checkInitializedArray('public values', publicValues); checkInitializedArray('generated values', generatedValues); var messageDigester = messageDigestFactory.getCryptoMessageDigest(); messageDigester.start(); messageDigester = update( messageDigester, this._arrayElementValuesToString(publicValues)); messageDigester = update( messageDigester, this._arrayElementValuesToString(generatedValues)); messageDigester = update(messageDigester, data); return messageDigester.digest(); } }; /** * Utility method that updates messageDigester with the provided string. */ function update(messageDigester, str) { var stringRepresentationB64 = converters.base64Encode(str); var arrayToHash = []; arrayToHash.push(stringRepresentationB64); messageDigester.update(arrayToHash); return messageDigester; } /** * Utility method that checks that value is defined and not null and throws * related exception otherwise. */ function checkDefined(key, value) { if (typeof value === 'undefined') { throw new exceptions.CryptoLibException( 'The ' + key + ' must be defined'); } } /** * Utility method that checks that value is initialized array and throws * related exception otherwise. */ function checkInitializedArray(key, value) { checkDefined(key, value); if (!isArray(value)) { throw new exceptions.CryptoLibException( 'The ' + key + ' must be an initialized array'); } } /** * Utility method that checks that value is not null and throws related * exception otherwise. */ function checkNotNull(key, value) { checkDefined(key, value); if (value === null) { throw new exceptions.CryptoLibException( 'The ' + key + ' must not be null'); } } /** * Utility method that checks that value is not empty and throws related * exception otherwise. */ function checkNotEmpty(key, value) { checkDefined(key, value); if (value.length === 0) { throw new exceptions.CryptoLibException( 'The ' + key + ' must not be empty'); } } /** * Utility method that checks that value is positive and throws related * exception otherwise. */ function checkIsPositive(key, value) { checkDefined(key, value); if (isNaN(value)) { throw new exceptions.CryptoLibException( 'The ' + key + ' must be a number; Found ' + value); } if (value <= 0) { throw new exceptions.CryptoLibException( 'The ' + key + ' must be positive; Found ' + value); } } /** * Utility method that checks that values are defined and throws related * exception otherwise. */ var checkDefinedProgressParams = function( progressCallback, progressPercentMinCheckInterval) { if (typeof progressCallback !== 'undefined' && typeof progressCallback !== 'function') { throw new exceptions.CryptoLibException( 'The progress callback function provided as input is not a function'); } if (typeof progressPercentMinCheckInterval !== 'undefined' && isNaN(progressPercentMinCheckInterval)) { throw new exceptions.CryptoLibException( 'The progress percentage check interval provided as input is not a number'); } }; function isArray(a) { return (!!a) && (a.constructor === Array); } /** * Utility method that creates a new instance of ProgressMeter. */ function createProgressMeter( progressMax, progressCallback, progressPercentMinCheckInterval) { checkDefinedProgressParams( progressCallback, progressPercentMinCheckInterval); return new utils.ProgressMeter( progressMax, progressCallback, progressPercentMinCheckInterval); } /** * Provides utility functionality for working with proofs. * * @class utils * @memberof proofs */ box.proofs.utils = (function() { /** * Deserializes a Proof JSON string representation to a Proof object. * * @function * @memberof proofs.utils * @param jsonAsString * {JSON} a JSON representation of a proof. * @returns a new Proof, created from the received string. */ function deserializeProof(jsonAsString) { var proofJson = JSON.parse(jsonAsString).zkProof; var q = converters.base64ToBigInteger(proofJson.q); var hash = new box.commons.mathematical.Exponent( q, converters.base64ToBigInteger(proofJson.hash)); var values = []; for (var i = 0; i < proofJson.values.length; i++) { values.push(new box.commons.mathematical.Exponent( q, converters.base64ToBigInteger(proofJson.values[i]))); } return new box.proofs.Proof(hash, values); } /** * Deserializes a ProofPreComputedValues JSON string representation to a * ProofPreComputedValues object. * * @function * @memberof proofs.utils * @param jsonAsString * {JSON} a JSON representation of a pre-computed value. * @returns a new ProofPreComputedValues, created from the received * string. */ function deserializeProofPreComputedValues(jsonAsString) { var proofPreComputedValuesJson = JSON.parse(jsonAsString).preComputed; var p = converters.base64ToBigInteger(proofPreComputedValuesJson.p); var q = converters.base64ToBigInteger(proofPreComputedValuesJson.q); var exponentValues = proofPreComputedValuesJson.exponents; var exponents = []; var value; for (var i = 0; i < exponentValues.length; i++) { value = converters.base64ToBigInteger(exponentValues[i]); exponents.push(new box.commons.mathematical.Exponent(q, value)); } var phiOutputValues = proofPreComputedValuesJson.phiOutputs; var phiOutputs = []; for (var j = 0; j < phiOutputValues.length; j++) { value = converters.base64ToBigInteger(phiOutputValues[j]); phiOutputs.push( new box.commons.mathematical.ZpGroupElement(value, p, q)); } return new box.proofs.ProofPreComputedValues(exponents, phiOutputs); } return { deserializeProof: deserializeProof, deserializeProofPreComputedValues: deserializeProofPreComputedValues }; })(); };