/* * 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 validator = require('./input-validator'); var constants = require('./constants'); var codec = require('scytl-codec'); var forge = require('node-forge'); var BigInteger = forge.jsbn.BigInteger; module.exports = SecureRandomGenerator; /** * @class SecureRandomGenerator * @classdesc The secure random generator API. To instantiate this object, use * the method {@link SecureRandomService.newRandomGenerator}. * @hideconstructor * @param {Prng} * prng The pseudo-random number generator (PRNG) to use. * @param {Object} * [options] An object containing optional arguments. * @param {number} * [options.maxNumBytes=512] The maximum number of bytes that can be * randomly generated per call. * @param {number} * [options.maxNumDigits=512] The maximum number of BigInteger digits * that can be randomly generated per call. */ function SecureRandomGenerator(prng, options) { options = options || {}; var maxNumBytes_ = options.maxNumBytes || constants.SECURE_RANDOM_MAX_NUM_BYTES; var maxNumDigits_ = options.maxNumDigits || constants.SECURE_RANDOM_MAX_NUM_DIGITS; /** * Generates some random bytes. * * @function nextBytes * @memberof SecureRandomGenerator * @param {number} * numBytes The number of bytes to generate. * @return {Uint8Array} The random bytes. * @throws {Error} * If the input data validation fails. */ this.nextBytes = function(numBytes) { checkData(numBytes, maxNumBytes_, 'Number of bytes to randomly generate'); return prng.nextBytes(numBytes); }; /** * Generates a random positive BigInteger object, with a specified maximum * number of bits. * * @function nextBigInteger * @memberof SecureRandomGenerator * @param {number} * maxNumBits The maximum number of bits in the BigInteger * object. * @returns {BigInteger} The random BigInteger object. * @throws {Error} * If the input data validation fails. */ this.nextBigInteger = function(maxNumBits) { validator.checkIsPositiveNumber( maxNumBits, 'Maximum number of bits in BigInteger to randomly generate'); // Find minimum number of bytes needed for maximum number of bits. var numBytes = Math.ceil(maxNumBits / 8); var numExcessBits = 8 - (maxNumBits % 8); var randomBytes = prng.nextBytes(numBytes); var randomByteArray = Array.apply([], randomBytes); randomByteArray.unshift(0); var generatedBigInteger = codec.bytesToBigInteger(randomBytes); return generatedBigInteger.shiftRight(numExcessBits); }; /** * Generates a random positive BigInteger object, with a specified maximum * number of digits. * * @function nextBigIntegerByDigits * @memberof SecureRandomGenerator * @param {number} * numDigits The maximum number of digits in the BigInteger * object value. * @returns {BigInteger} The random BigInteger object. * @throws {Error} * If the input data validation fails. */ this.nextBigIntegerByDigits = function(numDigits) { checkData( numDigits, maxNumDigits_, 'Number of digits in BigInteger to randomly generate'); // Variable n is largest possible number for given number of digits. For // instance, for 3 digits, n would be 999. var n = ((new BigInteger('10')).pow(numDigits)).subtract(BigInteger.ONE); // Get number of bits needed to represent n. var numBits = n.bitLength(); // Get minimum number of bytes needed to represent n. var numBytes = Math.ceil(numBits / 8); // Continuously generate numbers until number is found that is smaller than // n. Note: This will take just one round if number of bits is a multiple // of 8. var generatedBigInteger; do { var randomBytes = prng.nextBytes(numBytes); var randomByteArray = Array.apply([], randomBytes); randomByteArray.unshift(0); generatedBigInteger = codec.bytesToBigInteger(randomBytes); } while ((generatedBigInteger.compareTo(BigInteger.ZERO) <= 0) || (generatedBigInteger.compareTo(n) > 0)); return generatedBigInteger; }; /** * Implementation of the method getBytesSync so that this random * generator can be used directly by Forge. * * @function getBytesSync * @memberof SecureRandomGenerator * @private * @param {number} * numBytes The number of bytes to generate. * @returns {string} The random bytes, in binary encoded format. */ this.getBytesSync = function(numBytes) { return codec.binaryEncode(this.nextBytes(numBytes)); }; function checkData(num, max, label) { validator.checkIsPositiveNumber(num, label); if (num > max) { throw new Error( 'Expected ' + label + ' to be less than or equal to ' + max + ' ; Found: ' + num); } } }